Paquete de parámetros(desde C++11)
Un paquete de parámetros de plantilla es un parámetro de plantilla que acepta cero o más argumentos de plantilla (de no-tipo, tipos, o plantillas). Un paquete de parámetros de función es un parámetro de función que acepta cero o más argumentos de función.
Una plantilla con al menos un paquete de parámetros se le conoce como una plantilla variádica.
[editar] Sintaxis
Un paquete de parámetros de plantilla (aparece en las listas de parámetros de las plantillas de alias, plantillas de clase, plantillas de variable y plantillas de función).
tipo ... nombre-del-paquete(opcional)
|
(1) | ||||||||
typename | class ... nombre-del-paquete(opcional)
|
(2) | ||||||||
restricción-de-tipo ... nombre-del-paquete(opcional)
|
(3) | (desde C++20) | |||||||
template < lista-de-parámetros > class ... nombre-del-paquete(opcional)
|
(4) | (hasta C++17) | |||||||
template < lista-de-parámetros > typename | class ... nombre-del-paquete(opcional)
|
(4) | (desde C++17) | |||||||
Paquete de parámetros de función (una forma de un declarador, aparece en una lista de parámetros de función de una plantilla de función variádica).
nombre-del-paquete ... nombre-del-paquete-de-parámetros(opcional)
|
(5) | ||||||||
Expansión de paquete de parámetros (aparece en un cuerpo de una plantilla variádica).
patrón ...
|
(6) | ||||||||
3) Un paquete de parámetros de plantilla de tipo restringido con un nombre opcional.
|
(desde C++20) |
patrones
. El patrón debe incluir al menos un paquete de parámetros.[editar] Explicación
Una plantilla de clase variádica puede instanciarse con cualquier número de argumentos de plantilla:
template<class... Tipos> struct Tupla {}; Tupla<> t0; // Tipos no contiene argumentos Tupla<int> t1; // Tipos contiene un argumento: int Tupla<int, float> t2; // Tipos contiene dos argumentos: int y float Tupla<0> error; // error: 0 no es un tipo
Una plantilla de función variádica puede llamarse con cualquier número de argumentos de función (los argumentos de plantilla se deducen a través de la deducción de argumentos de plantilla):
template<class... Tipos> void f(Tipos... args); f(); // de acuerdo: args no contiene argumentos f(1); // de acuerdo: args contiene un argumento: int f(2, 1.0); // de acuerdo: args contiene dos argumentos: int y double
En una plantilla de clase primaria, el paquete de parámetros de plantilla debe ser el parámetro final en la lista de parámetros de plantilla. En una plantilla de función, el paquete de parámetros de plantilla puede aparecer antes en la lista dado que todos los parámetros siguientes puedan deducirse de los argumentos de función, o tengan argumentos por defecto:
template<typename U, typename... Ts> // correcto: se puede deducir U struct valida; //template<typename... Ts, typename U> // error: Ts.. no está al final // struct Invalida; template<typename... Ts, typename U, typename=void> void valida(U, Ts...); // de acuerdo: puede deducirse U // void valida(Ts..., U); // no se puede usar: Ts... es un contexto no deducido en esta posición valida(1.0, 1, 2, 3); // de acuerdo: deduce U como double, Ts como {int, int, int}
Si cada especialización válida de una plantilla variádica requiere un paquete de parámetros de plantilla vacío, el programa está mal formado, no se requiere diagnóstico.
[editar] Expansión de paquete
Un patrón seguido de puntos suspensivos, en el que el nombre de al menos un paquete de parámetros aparece al menos una vez, se expande a cero o más instancias del patrón separadas por comas, donde el nombre del paquete de parámetros se reemplaza por cada uno de los elementos del paquete, en orden.
template<class... Us> void f(Us... pargs) {} template<class... Ts> void g(Ts... args) { f(&args...); // “&args...” es una expansión de paquete // “&args” es su patrón } g(1, 0.2, "a"); // Ts... args se expande en int E1, double E2, const char* E3 // &args... se expande en &E1, &E2, &E3 // Us... pargs se expande en int* E1, double* E2, const char** E3
Si los nombres de dos paquetes de parámetros aparecen en el mismo patrón, se expanden simultáneamente y deben tener la misma longitud:
template<typename...> struct Tupla {}; template<typename T1, typename T2> struct Par {}; template<class... Args1> struct comprimir { template<class... Args2> struct con { typedef Tupla<Par<Args1, Args2>...> type; // Par<Args1, Args2>... es la expansión de paquete // Par<Args1, Args2> es el patrón }; }; typedef comprimir<short, int>::con<unsigned short, unsigned>::type T1; // Par<Args1, Args2>... se expande en // Par<short, unsigned short>, Par<int, unsigned int> // T1 es Tupla<Par<short, unsigned short>, Par<int, unsigned>> // typedef comprimir<short>::con<unsigned short, unsigned>::type T2; // error: expansión de paquete contiene paquetes de parámetros de distintas longitudes
Si una expansión de paquete está anidada dentro de otra expansión de paquete, los paquetes de parámetros que aparecen dentro de la expansión de paquete más interna son expandidos por ella, y debe haber otro paquete mencionado en la expansión de paquete circundante, pero no en la más interna:
template<class... Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); // const_cast<const Args*>(&args) es el patrón, expande dos paquetes // (Args y args) simultáneamente f(h(args...) + args...); // expansión de paquete anidada: // la expansión de paquete interna es "args...", se expande primero // la expansión de paquete externa es h(E1, E2, E3) + args..., se expande // después (como h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3) }
[editar] Lugares de expansión
Dependiendo de dónde ocurra la expansión, la lista separada por comas resultante es un tipo de lista diferente: lista de parámetros de función, lista de inicializadores de miembros, lista de atributos, etc. La siguiente es la lista de todos los contextos admitidos:
[editar] Listas de argumentos de función
Una expansión de paquete puede aparecer dentro de los paréntesis del operador de llamada a función, en cuyo caso la expresión más larga o la lista de inicializadores entre llaves a la izquierda de los puntos suspensivos es el patrón que se expande:
f(&args...); // se expande en f(&E1, &E2, &E3) f(n, ++args...); // se expande en f(n, ++E1, ++E2, ++E3); f(++args..., n); // se expande en f(++E1, ++E2, ++E3, n); f(const_cast<const Args*>(&args)...); // f(const_cast<const E1*>(&X1), const_cast<const E2*>(&X2), const_cast<const E3*>(&X3)) f(h(args...) + args...); // se expande en // f(h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
Formalmente, la lista-de-expresiones en una expresión de llamada a función se clasifica como una lista-de-inicializadores, y el patrón es la cláusula-inicializadora, que es o bien una expresión-de-asignación, o una lista-de-inicializadores-entre-llaves.
[editar] Inicializadores entre paréntesis
Una expansión de paquete puede aparecer dentro de los paréntesis de un inicializador directo, una conversión estilo función, y otros contextos (inicializador de miembro, expresión new, etc.) en cuyo caso las reglas son idénticas a las reglas para una expresión de llamada a función mencionada anteriormente:
Clase c1(&args...); // llama a Clase::Clase(&E1, &E2, &E3) Clase c2 = Clase(n, ++args...); // llama a Clase::Clase(n, ++E1, ++E2, ++E3); ::new((void *)p) U(std::forward<Args>(args)...) // std::allocator::allocate
[editar] Inicializadores entre llaves
Una expansión de paquete también puede aparecer en una lista-de-inicializadores-entre-llaves (una lista entre llaves de inicializadores y otras listas-de-inicializadores-entre-llaves, utilizada en la inicialización de lista y algunos otros contextos):
template<typename... Ts> void func(Ts... args) { const int size = sizeof...(args) + 2; int res[size] = {1, args..., 2}; // dado que las listas de inicializadores garantizan la secuenciación, // esto se puede usar para llamar a una función en cada elemento de un paquete, en orden int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...}; }
[editar] Listas de argumentos de plantilla
Las expansiones de paquetes se pueden usar en cualquier lugar de una lista de argumentos de plantilla, siempre que la plantilla tenga los parámetros para coincidir con la expansión:
template<class A, class B, class... C> void func(A arg1, B arg2, C... arg3) { container<A, B, C...> t1; // se expande en container<A, B, E1, E2, E3> container<C..., A, B> t2; // se expande en container<E1, E2, E3, A, B> container<A, C..., B> t3; // se expande en container<A, E1, E2, E3, B> }
[editar] Lista de parámetros de función
En una lista de parámetros de función, si aparecen puntos suspensivos en una declaración de parámetros (ya sea que nombre un paquete de parámetros de función (como en, Args...
args) o no) la declaración del parámetros es el patrón:
template<typename... Ts> void f(Ts...) {} f('a', 1); // Ts... se expande en void f(char, int) f(0.1); // Ts... se expande en void f(double) template<typename... Ts, int... N> void g(Ts (&...arr)[N]) {} int n[1]; g<const char, int>("a", n); // Ts (&...arr)[N] se expande en // const char (&)[2], int(&)[1]
Nota: En el patrón Ts (&...arr)[N]
, los puntos suspensivos son el elemento más interno, no el último elemento como en todas las demás expansiones del paquete.
Nota: Ts (&...)[N]
no está permitido porque la gramática de C++11 requiere que los puntos suspensivos entre paréntesis tengan un nombre: CWG issue 1488.
[editar] Lista de parámetros de plantilla
La expansión de paquete puede aparecer en una lista de parámetros de plantilla:
template<typename... T> struct poseedor_de_valor { template<T... Valores> // se expande en una lista de parámetros de plantilla struct apply {}; // de no-tipo, como <int, char, int(&)[5]> };
[editar] Especificadores base y listas de inicializadores de miembros
Una expansión de paquete puede designar la lista de clases base en una declaración de una clase. Por lo general, esto también significa que el constructor necesita usar una expansión de paquete en la lista de inicializadores de miembros para llamar a los constructores de estas bases:
template<class... Mezclas> class X : public Mezclas... { public: X(const Mezclas&... mezclas) : Mezclas(mezclas)... {} };
[editar] Capturas de lambda
La expansión de paquete puede aparecer en cláusula de captura de una expresión lambda:
template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
[editar] El operador sizeof...
El operador sizeof...
también se clasifica como una expansión de paquete:
template<class... Tipos> struct conteo { static const std::size_t value = sizeof...(Tipos); };
Especificaciones de excepción dinámicaLa lista de excepciones en una especificación de excepción dinámica también puede ser una expansión de paquete: template<class...X> void func(int arg) throw(X...) { // ... lanza diferentes X en situaciones distintas } |
(hasta C++17) |
[editar] Especificador de alineación
Se admiten las expansiones de paquetes tanto en las listas de tipos como en las listas de expresiones utilizadas por la palabra clave alignas
.
[editar] Listas de atributos
Se admiten las expansiones de paquetes en las listas de atributos, si está permitido por la especificación de atributos. Por ejemplo:
template<int... args> [[vendor::attr(args)...]] void* f();
Expresiones de pliegueEn las expresiones de pliegue, el patrón es toda la subexpresión que no contiene un paquete de parámetros sin expandir. Declaraciones usingEn una declaración using, pueden aparecer los puntos suspensivos en la lista de declaradores. Esto es útil cuando se deriva de un paquete de parámetros: template <typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // de acuerdo: B::g y D::g ya se han presentado |
(desde C++17) |
[editar] Notas
Esta sección está incompleta Razón: Agregar algunas palabras acerca de las especializaciones parciales y otras maneras de acceder a los elementos individuales. Mencionar recursión frente a logarítmicas y frente acceso directo como expresiones de pliegue. |
Macro de prueba de característica | Valor | Estándar | Comentario |
---|---|---|---|
__cpp_variadic_templates |
200704L | (C++11) | Plantillas variádicas |
[editar] Ejemplo
El siguiente ejemplo define una función similar a std::printf, que reemplaza cada aparición del carácter %
en la cadena de formato con un valor.
Se llama a la primera sobrecarga cuando solo se pasa la cadena de formato y no hay expansión de parámetros.
La segunda sobrecarga contiene un parámetro de plantilla separado para el encabezado de los argumentos y un paquete de parámetros, esto permite que la llamada recursiva pase solo la cola de los parámetros hasta que quede vacía.
Targs
es el paquete de parámetros de plantilla y Fargs
es el paquete de parámetros de función.
#include <iostream> void tprintf(const char* formato) // función base { std::cout << formato; } template<typename T, typename... Targs> void tprintf(const char* formato, T valor, Targs... Fargs) // función variádica recursiva { for (; *formato != '\0'; formato++) { if (*formato == '%') { std::cout << valor; tprintf(formato + 1, Fargs...); // llamada recursiva return; } std::cout << *formato; } } int main() { tprintf("% mundo% %\n", "Hola", '!', 123); }
Salida:
Hola mundo! 123
[editar] Reportes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
---|---|---|---|
CWG 1533 | C++11 | una expansión de paquete podría ocurrir en un inicializador de miembro para un miembro | no permitdo |
[editar] Véase también
plantilla de función | |
plantilla de clase | |
sizeof...
|
Consulta el número de elementos en un paquete de parámetros. |
Funciones variádicas estilo C | |
Las macros de preprocesador | también pueden ser variádicas |
Expresiones de pliegue |