c++ - todas - tipos de funciones en c
Llamar a una funciĆ³n para cada argumento de plantilla variadica y una matriz (5)
Bonita plantilla como respuesta para la primera parte de la pregunta:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((f(std::forward<Args>(args)), 0)...);
}
Entonces tengo un tipo X
:
typedef ... X;
y una función de plantilla f
:
class <typename T>
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<typename... Args>
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<class T> X fv(const T& t) { X x; f(x,t); return x; }
template<class... Args>
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)
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<typename ...T> pass(T...) {} };
template<typename... Args>
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.
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<int... I> struct index {
template<int n> using append = index<I..., n>; };
template<int N> struct make_index { typedef typename
make_index<N - 1>::type::template append<N - 1> type; };
template<> struct make_index<0> { typedef index<> type; };
template<int N> using indexer = typename make_index<N>::type;
template<typename... Args, int... i>
void h2(index<i...>, 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<typename... Args>
void h(Args... args)
{
h2(indexer<sizeof...(args)>(), std::forward<Args>(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
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 <int Index, int Size>
void h_aux(X (&)[Size]) {
}
template <int Index, int Size, typename Arg, typename... Args>
void h_aux(X (&xs)[Size], Arg arg, Args... args) {
f(xs[Index], arg);
h_aux<Index + 1, Size>(xs, args...);
}
template <typename... Args>
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 compilador que debe ser una expresión constante.
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<class T>
X fw(T const& t){ X x; f(x, t); return x; }
template<class... Args>
void h(Args... args){
X xs[] = { fw(args)... };
g(xs, sizeof...(Args));
}
Y si pudieras cambiar g
para aceptar una std::initializer_list
, sería aún más conciso:
template<class... Args>
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<X> const& xs){ g(xs.begin(), xs.size()); }
template<class... Args>
void h(Args... args){
g({f(args)...});
}
Ejemplo en vivo
Editar: Otra opción es usar una matriz temporal:
template<class T>
using Alias = T;
template<class T>
T& as_lvalue(T&& v){ return v; }
template<class... Args>
void h(Args... args){
g(as_lvalue(Alias<X[]>{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 <tuple>
template<unsigned> struct uint_{}; // compile-time integer for "iteration"
template<unsigned N, class Tuple>
void h_helper(X (&)[N], Tuple const&, uint_<N>){}
template<unsigned N, class Tuple, unsigned I = 0>
void h_helper(X (&xs)[N], Tuple const& args, uint_<I> = {}){
f(xs[I], std::get<I>(args));
h_helper(xs, args, uint_<I+1>());
}
template<typename... Args>
void h(Args... args)
{
static constexpr unsigned nargs = sizeof...(Args);
X xs[nargs];
h_helper(xs, std::tie(args...));
g(xs, nargs);
}
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<unsigned... Indices>
struct indices{
using next = indices<Indices..., sizeof...(Indices)>;
};
template<unsigned N>
struct build_indices{
using type = typename build_indices<N-1>::type::next;
};
template <>
struct build_indices<0>{
using type = indices<>;
};
template<unsigned N>
using IndicesFor = typename build_indices<N>::type;
template<unsigned N, unsigned... Is, class... Args>
void f_them_all(X (&xs)[N], indices<Is...>, Args... args){
int unused[] = {(f(xs[Is], args), 1)...};
(void)unused;
}
template<class... Args>
void h(Args... args){
static constexpr unsigned nargs = sizeof...(Args);
X xs[nargs];
f_them_all(xs, IndicesFor<nargs>(), args...);
g(xs, nargs);
}
Xeo está en la idea correcta: desea construir algún tipo de "iterador variadic" que oculte una gran cantidad de esta maldad del resto 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.