c++ - Expansión con plantillas variadic
variadic c++ (2)
Esta pregunta ya tiene una respuesta aquí:
¿Cuál es la diferencia entre las siguientes 3 llamadas para gun
función de gun
?
template <class... Ts> void fun(Ts... vs) {
gun(A<Ts...>::hun(vs)...);
gun(A<Ts...>::hun(vs...));
gun(A<Ts>::hun(vs)...);
}
Estoy interesado en una respuesta que explique las tres llamadas usando un ejemplo específico.
Así es como se expanden cuando Ts es T, U y vs es t, u:
gun(A<Ts...>::hun(vs)...) -> gun(A<T, U>::hun(t), A<T, U>::hun(u))
gun(A<Ts...>::hun(vs...)) -> gun(A<T, U>::hun(t, u));
gun(A<Ts>::hun(vs)...) -> gun(A<T>::hun(t), A<U>::hun(u))
Y un caso más que no cubrió:
gun(A<Ts>::hun(vs...)...) -> gun(A<T>::hun(t, u), A<U>::hun(t, u))
Si ejecuta el siguiente código en VS14 obtendrá este resultado:
calling gun(A<Ts...>::hun(vs)...);
struct A<int,double>::hun(double);
struct A<int,double>::hun(int);
gun(struct A<int,double>, struct A<int,double>);
calling gun(A<Ts...>::hun(vs...));
struct A<int,double>::hun(int, double);
gun(struct A<int,double>);
calling gun(A<Ts>::hun(vs)...);
struct A<double>::hun(double);
struct A<int>::hun(int);
gun(struct A<int>, struct A<double>);
calling gun(A<Ts>::hun(vs...)...);
struct A<double>::hun(int, double);
struct A<int>::hun(int, double);
gun(struct A<int>, struct A<double>);
Código:
#include <iostream>
#include <typeinfo>
using namespace std;
void printTypes() {}
template<typename T, typename... Ts> void printTypes(T, Ts... vs) {
cout << typeid(T).name() << (sizeof...(Ts) ? ", " : "");
printTypes(vs...);
}
template<typename... Ts> struct A {
template<typename... Us>
static auto hun(Us... vs) {
cout << " " << typeid(A).name() << "::hun(";
printTypes(vs...);
cout << ");" << endl;
return A{};
}
};
template<typename... Ts> void gun(Ts... vs) {
cout << " gun(";
printTypes(vs...);
cout << ");" << endl;
}
template<typename... Ts> void fun(Ts... vs) {
cout << "calling gun(A<Ts...>::hun(vs)...);" << endl;
gun(A<Ts...>::hun(vs)...);
cout << "calling gun(A<Ts...>::hun(vs...));" << endl;
gun(A<Ts...>::hun(vs...));
cout << "calling gun(A<Ts>::hun(vs)...);" << endl;
gun(A<Ts>::hun(vs)...);
cout << "calling gun(A<Ts>::hun(vs...)...);" << endl;
gun(A<Ts>::hun(vs...)...);
}
int main() {
fun(1, 2.0);
}
Originalmente, simplemente respondí literalmente la pregunta, pero quería expandir esto de alguna manera para proporcionar una explicación más completa de cómo se expanden qué paquetes se convierten en qué. Así es como pienso sobre las cosas de todos modos.
Cualquier paquete inmediatamente seguido de una elipsis simplemente se expande en su lugar. Entonces A<Ts...>
es equivalente a A<T1, T2, ..., TN>
y hun(vs...)
es similar a hun(v1, v2, ..., vn)
. Donde se complica es cuando en lugar de un paquete seguido de elipsis obtienes algo así como ((expr)...)
. Esto se expandirá a (expr1, expr2, ..., exprN)
donde expri
refiere a la expresión original con cualquier paquete reemplazado por la i
ésima versión. Entonces, si tienes hun((vs+1)...)
, eso se convierte en hun(v1+1, v2+1, ..., vn+1)
. Donde se vuelve más divertido es que expr
puede contener más de un paquete (¡siempre y cuando todos tengan el mismo tamaño!). Así es como implementamos el modelo estándar de reenvío perfecto;
foo(std::forward<Args>(args)...)
Aquí expr
contiene dos paquetes ( Args
y args
son ambos paquetes) y la expansión "itera" sobre ambos:
foo(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), ..., std::forward<ArgN>(argN));
Ese razonamiento debería permitir recorrer rápidamente sus casos para, digamos, lo que sucede cuando llama a foo(1, 2, ''3'')
.
El primero, gun(A<Ts...>::hun(vs)...);
expande Ts
"en su lugar" y luego hay una expresión para expandir para las últimas elipsis, por lo que esto llama:
gun(A<int, int, char>::hun(1),
A<int, int, char>::hun(2),
A<int, int, char>::hun(''3''));
El segundo, gun(A<Ts...>::hun(vs...));
expande ambos paquetes en su lugar:
gun(A<int, int, char>::hun(1, 2, ''3''));
El tercero, gun(A<Ts>::hun(vs)...)
, expande ambos paquetes al mismo tiempo:
gun(A<int>::hun(1),
A<int>::hun(2),
A<char>::hun(''3''));
[actualización] Para completar, el gun(A<Ts>::hun(vs...)...)
llamaría:
gun(A<int>::hun(1, 2, ''3''),
A<int>::hun(1, 2, ''3''),
A<char>::hun(1, 2, ''3''));
Finalmente, hay un último caso para considerar dónde vamos por la borda en las elipsis:
gun(A<Ts...>::hun(vs...)...);
Esto no compilará Expandimos ambos Ts
y vs
"in situ", pero luego no tenemos paquetes para expandir para las elipses finales.