Llamar a una función para cada argumento de plantilla variadica y una matriz

Entonces tengo un tipo X :

 typedef ... X; 

y una función de plantilla f :

 class  void f(X& x_out, const T& arg_in); 

y luego una función g :

 void g(const X* x_array, size_t x_array_size); 

Necesito escribir una función de plantilla variadica h que haga esto:

 template void h(Args... args) { constexpr size_t nargs = sizeof...(args); // get number of args X x_array[nargs]; // create X array of that size for (int i = 0; i < nargs; i++) // foreach arg f(x_array[i], args[i]); // call f (doesn't work) g(x_array, nargs); // call g with x_array } 

La razón por la que no funciona es porque no puede subindicar args como ese en el tiempo de ejecución.

¿Cuál es la mejor técnica para reemplazar la parte media de h ?

Y el ganador es Xeo:

 template X fv(const T& t) { X x; f(x,t); return x; } template void h(Args... args) { X x_array[] = { fv(args)... }; g(x_array, sizeof...(Args)); } 

(De hecho, en mi caso específico, puedo volver a escribir f para devolver x por valor en lugar de como un parámetro de salida, por lo que ni siquiera necesito el valor de fv anterior)

    Podrías refactorizar o envolver f para devolver una nueva X lugar de tenerla aprobada, ya que esto jugaría la expansión del paquete en la mano y haría la función realmente concisa:

     template X fw(T const& t){ X x; f(x, t); return x; } template void h(Args... args){ X xs[] = { fw(args)... }; g(xs, sizeof...(Args)); } 

    Ejemplo en vivo

    Y si pudieras cambiar g para aceptar una std::initializer_list , sería aún más conciso:

     template void h(Args... args){ g({f(args)...}); } 

    Ejemplo en vivo O (quizás mejor), también podría proporcionar solo un envoltorio g que se reenvía a la verdadera g :

     void g(X const*, unsigned){} void g(std::initializer_list const& xs){ g(xs.begin(), xs.size()); } template void h(Args... args){ g({f(args)...}); } 

    Ejemplo en vivo
    Editar: Otra opción es usar una matriz temporal:

     template using Alias = T; template T& as_lvalue(T&& v){ return v; } template void h(Args... args){ g(as_lvalue(Alias{f(args)...}), sizeof...(Args)); } 

    Ejemplo en vivo Tenga en cuenta que la función as_lvalue es peligrosa, la matriz aún solo vive hasta el final de la expresión completa (en este caso g ), así que tenga cuidado al usarla. El Alias es necesario ya que solo X[]{ ... } no está permitido debido a la gramática del lenguaje.

    Si todo eso no es posible, necesitarás recursividad para acceder a todos los elementos del paquete args .

     #include  template struct uint_{}; // compile-time integer for "iteration" template void h_helper(X (&)[N], Tuple const&, uint_){} template void h_helper(X (&xs)[N], Tuple const& args, uint_ = {}){ f(xs[I], std::get(args)); h_helper(xs, args, uint_()); } template void h(Args... args) { static constexpr unsigned nargs = sizeof...(Args); X xs[nargs]; h_helper(xs, std::tie(args...)); g(xs, nargs); } 

    Ejemplo en vivo

    Editar: Inspirado por el comentario de ecatmur, utilicé el truco de los índices para que funcione solo con la expansión del paquete y con f y g tal cual, sin alterarlos.

     template struct indices{ using next = indices; }; template struct build_indices{ using type = typename build_indices::type::next; }; template <> struct build_indices<0>{ using type = indices<>; }; template using IndicesFor = typename build_indices::type; template void f_them_all(X (&xs)[N], indices, Args... args){ int unused[] = {(f(xs[Is], args), 1)...}; (void)unused; } template void h(Args... args){ static constexpr unsigned nargs = sizeof...(Args); X xs[nargs]; f_them_all(xs, IndicesFor(), args...); g(xs, nargs); } 

    Ejemplo en vivo

    Es obvio: no usas iteración sino recurrencia. Cuando se trata de plantillas variadic, siempre aparece algo recursivo. Incluso cuando se vinculan los elementos a std::tuple< ...> usando tie() es recursivo: sucede que el negocio recursivo lo realiza la tupla. En tu caso, parece que quieres algo como esto (probablemente haya algunos errores tipográficos, pero en general esto debería funcionar):

     template  void h_aux(X (&)[Size]) { } template  void h_aux(X (&xs)[Size], Arg arg, Args... args) { f(xs[Index], arg); h_aux(xs, args...); } template  void h(Args... args) { X xs[sizeof...(args)]; h_aux<0, sizeof...(args)>(xs, args...); g(xs, sizeof...(args)); } 

    Creo que tampoco podrás usar nargs para definir el tamaño de la matriz: Nada indica al comstackdor que debe ser una expresión constante.

    Es bastante simple de hacer con la expansión del paquete de parámetros, incluso si no puede reescribir f para devolver el parámetro de salida por valor:

     struct pass { template pass(T...) {} }; template void h(Args... args) { const size_t nargs = sizeof...(args); // get number of args X x_array[nargs]; // create X array of that size X *x = x_array; int unused[]{(f(*x++, args), 1)...}; // call f pass{unused}; g(x_array, nargs); // call g with x_array } 

    Debería ser posible solo escribir

      pass{(f(*x++, args), 1)...}; // call f 

    pero parece que g ++ (4.7.1 al menos) tiene un error en el que no ordena la evaluación de los parámetros de la lista de inicialización de llaves como iniciadores de clase. Los inicializadores de matriz están bien; ver Secuenciación entre una expansión variada para más información y ejemplos.

    Ejemplo en vivo


    Como alternativa, esta es la técnica mencionada por Xeo usando un paquete de índice generado; desafortunadamente requiere una llamada de función adicional y un parámetro, pero es razonablemente elegante (especialmente si tiene un generador de paquete de índice alrededor):

     template struct index { template using append = index; }; template struct make_index { typedef typename make_index::type::template append type; }; template<> struct make_index<0> { typedef index<> type; }; template using indexer = typename make_index::type; template void h2(index, Args... args) { const size_t nargs = sizeof...(args); // get number of args X x_array[nargs]; // create X array of that size pass{(f(x_array[i], args), 1)...}; // call f g(x_array, nargs); // call g with x_array } template void h(Args... args) { h2(indexer(), std::forward(args)...); } 

    Ver C ++ 11: puedo pasar de múltiples args a tuple, pero ¿puedo pasar de tuple a multiple args? para más información. Ejemplo en vivo

    Bonita plantilla como respuesta para la primera parte de la pregunta:

     template  void for_each_argument(F f, Args&&... args) { [](...){}((f(std::forward(args)), 0)...); } 

    Xeo está en la idea correcta: desea construir algún tipo de “iterador variadic” que oculte una gran cantidad de esta maldad del rest del código.

    Tomaría el material de índice y lo escondería detrás de una interfaz de iterador modelada a partir de std :: vector, ya que una std :: tuple también es un contenedor lineal de datos. Luego puede reutilizar todas sus funciones y clases variadas sin tener que tener un código explícitamente recursivo en ningún otro lado.