Pack (since C++11)
Un pack es una entidad de C++ que define una de las siguientes opciones:
- un paquete de parámetros
-
- paquete de parámetros de plantilla
- paquete de parámetros de función
| (desde C++20) |
| (desde C++26) |
Un paquete de parámetros de plantilla es un parámetro de plantilla que acepta cero o más argumentos de plantilla (constantes, 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 captura de inicialización de paquete lambda es una captura lambda que introduce una captura de inicialización para cada uno de los elementos en la expansión de paquete de su inicializador. |
(since C++20) |
|
Un paquete de enlace estructurado es un identificador en la declaración de enlace estructurado que introduce cero o más enlaces estructurados. |
(since C++26) |
El número de elementos de un pack es igual a:
- el número de argumentos proporcionados para el paquete de parámetros, si el paquete es un paquete de parámetros de plantilla o función,
|
(since C++20) |
|
(since C++26) |
Una plantilla con al menos un paquete de parámetros se denomina plantilla variádica .
Sintaxis
Paquete de parámetros de plantilla (aparece en alias template , class template , variable template (since C++14) , concept (since C++20) y function template parameter lists)
type
...
pack-name
(opcional)
|
(1) | ||||||||
typename
|
class
...
pack-name
(opcional)
|
(2) | ||||||||
type-constraint
...
pack-name
(opcional)
|
(3) | (desde C++20) | |||||||
template
<
parameter-list
>
class
...
pack-name
(opcional)
|
(4) | (hasta C++17) | |||||||
template
<
parameter-list
>
typename
|
class
...
pack-name
(opcional)
|
(4) | (desde C++17) | |||||||
Paquete de parámetros de función (una forma de declarador , aparece en la lista de parámetros de función de una plantilla de función variádica)
pack-name
...
pack-param-name
(opcional)
|
(5) | ||||||||
|
Para la sintaxis de paquetes no-paramétricos, consulte lambda init-capture pack y structured binding pack (desde C++26) . |
(desde C++20) |
Expansión de paquete (aparece en el cuerpo de una plantilla)
pattern
...
|
(6) | ||||||||
|
3)
Un
paquete de parámetros de plantilla de tipo restringido
con un nombre opcional
|
(since C++20) |
pattern
s. El patrón debe incluir al menos un paquete.
Explicación
Una plantilla de clase variádica puede instanciarse con cualquier número de argumentos de plantilla:
template<class... Types> struct Tuple {}; Tuple<> t0; // Types no contiene argumentos Tuple<int> t1; // Types contiene un argumento: int Tuple<int, float> t2; // Types contiene dos argumentos: int y float Tuple<0> t3; // error: 0 no es un tipo
Una plantilla de función variádica puede ser llamada con cualquier número de argumentos de función (los argumentos de plantilla se deducen mediante deducción de argumentos de plantilla ):
template<class... Types> void f(Types... args); f(); // Correcto: args no contiene argumentos f(1); // Correcto: args contiene un argumento: int f(2, 1.0); // Correcto: 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 siempre que todos los parámetros siguientes puedan deducirse de los argumentos de la función, o tengan argumentos predeterminados:
template<typename U, typename... Ts> // OK: puede deducir U struct valid; // template<typename... Ts, typename U> // Error: Ts... no está al final // struct Invalid; template<typename... Ts, typename U, typename=void> void valid(U, Ts...); // OK: puede deducir U // void valid(Ts..., U); // No se puede usar: Ts... es un contexto no deducible en esta posición valid(1.0, 1, 2, 3); // OK: 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.
Expansión de paquete
Un patrón seguido por puntos suspensivos, en el que el nombre de al menos un pack aparece al menos una vez, es expandido en cero o más instanciaciones del patrón, donde el nombre del pack es reemplazado por cada uno de los elementos del pack, en orden. Las instanciaciones de especificadores de alineación están separadas por espacios, otras instanciaciones están separadas por comas.
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 a int E1, double E2, const char* E3 // &args... se expande a &E1, &E2, &E3 // Us... pargs se expande a int* E1, double* E2, const char** E3
Si los nombres de dos paquetes aparecen en el mismo patrón, se expanden simultáneamente y deben tener la misma longitud:
template<typename...> struct Tuple {}; template<typename T1, typename T2> struct Pair {}; template<class... Args1> struct zip { template<class... Args2> struct with { typedef Tuple<Pair<Args1, Args2>...> type; // Pair<Args1, Args2>... es la expansión del paquete // Pair<Args1, Args2> es el patrón }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1; // Pair<Args1, Args2>... se expande a // Pair<short, unsigned short>, Pair<int, unsigned int> // T1 es Tuple<Pair<short, unsigned short>, Pair<int, unsigned>> // typedef zip<short>::with<unsigned short, unsigned>::type T2; // error: la expansión del paquete contiene paquetes de longitudes diferentes
Si una expansión de paquete está anidada dentro de otra expansión de paquete, los paquetes 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 envolvente, 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 interna del paquete es "args...", se expande primero // la expansión externa del paquete es h(E1, E2, E3) + args..., se expande // segundo (como h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3) }
Cuando el número de elementos en un pack es cero (pack vacío), la instanciación de una expansión de pack no altera la interpretación sintáctica del constructo que la contiene, incluso en casos donde omitir la expansión de pack por completo sería incorrecto o resultaría en una ambigüedad sintáctica. La instanciación produce una lista vacía.
template<class... Bases> struct X : Bases... { }; template<class... Args> void f(Args... args) { X<Args...> x(args...); } template void f<>(); // CORRECTO, X<> no tiene clases base // x es una variable de tipo X<> que está inicializada por valor
Loci de expansión
Dependiendo de dónde ocurra la expansión, la lista resultante separada por comas (o separada por espacios para especificadores de alineación ) es un tipo diferente de lista: 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 permitidos:
Listas de argumentos de función
Una expansión de paquete puede aparecer dentro de los paréntesis de un operador de llamada a función, en cuyo caso la expresión más grande o lista de inicialización entre llaves a la izquierda de los puntos suspensivos es el patrón que se expande:
f(args...); // se expande a f(E1, E2, E3) f(&args...); // se expande a f(&E1, &E2, &E3) f(n, ++args...); // se expande a f(n, ++E1, ++E2, ++E3); f(++args..., n); // se expande a 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 a // f(h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
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 mencionadas anteriormente:
Class c1(&args...); // llama a Class::Class(&E1, &E2, &E3) Class c2 = Class(n, ++args...); // llama a Class::Class(n, ++E1, ++E2, ++E3); ::new((void *)p) U(std::forward<Args>(args)...) // std::allocator::allocate
Inicializadores entre llaves
En una lista de inicializadores entre llaves, una expansión de paquete también puede aparecer:
template<typename... Ts> void func(Ts... args) { const int size = sizeof...(args) + 2; int res[size] = {1, args..., 2}; // dado que las listas de inicialización garantizan el ordenamiento, esto puede usarse para // llamar a una función en cada elemento de un paquete, en orden: int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...}; }
Listas de argumentos de plantilla
Las expansiones de paquetes pueden utilizarse en cualquier parte 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 a container<A, B, E1, E2, E3> container<C..., A, B> t2; // se expande a container<E1, E2, E3, A, B> container<A, C..., B> t3; // se expande a container<A, E1, E2, E3, B> }
Lista de parámetros de función
En una lista de parámetros de función, si aparece una elipsis en una declaración de parámetro (ya sea que nombre un function parameter pack (como en,
Args
...
args
) o no) la declaración del parámetro es el patrón:
template<typename... Ts> void f(Ts...) {} f('a', 1); // Ts... se expande a void f(char, int) f(0.1); // Ts... se expande a 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 a // const char (&)[2], int(&)[1]
Nota: En el patrón
Ts (&...arr)[N]
, la elipsis es el elemento más interno, no el último elemento como en todas las demás expansiones de paquetes.
Nota:
Ts (&...)[N]
no está permitido porque la gramática de C++11 requiere que elipsis entre paréntesis tenga un nombre:
CWG issue 1488
.
Lista de parámetros de plantilla
La expansión de paquetes puede aparecer en una lista de parámetros de plantilla:
template<typename... T> struct value_holder { template<T... Values> // se expande a un parámetro de plantilla constante struct apply {}; // lista, como <int, char, int(&)[5]> };
Especificadores de base y listas de inicialización de miembros
Una expansión de paquete puede designar la lista de clases base en una declaración de clase . Típicamente, 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... Mixins> class X : public Mixins... { public: X(const Mixins&... mixins) : Mixins(mixins)... {} };
Capturas de lambda
La expansión de paquetes puede aparecer en la cláusula de captura de una expresión lambda :
template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
El operador sizeof...
El operador
sizeof...
también se clasifica como una expansión de paquete:
template<class... Types> struct count { static const std::size_t value = sizeof...(Types); };
Especificaciones dinámicas de excepcionesLa lista de excepciones en una especificación dinámica de excepciones también puede ser una expansión de paquete: template<class... X> void func(int arg) throw(X...) { // ... throw different Xs in different situations } |
(hasta C++17) |
Especificador de alineación
Las expansiones de paquetes están permitidas tanto en las listas de tipos como en las listas de expresiones utilizadas por la palabra clave
alignas
. Las instanciaciones están separadas por espacios:
template<class... T> struct Align { alignas(T...) unsigned char buffer[128]; }; Align<int, short> a; // los especificadores de alineación después de la expansión son // alignas(int) alignas(short) // (sin coma entre ellos)
Lista de atributos
Las expansiones de paquetes están permitidas en las listas de atributos , si está permitido por la especificación del atributo. Por ejemplo:
template<int... args> [[vendor::attr(args)...]] void* f();
Expresiones de plegadoEn las expresiones de plegado , el patrón es la subexpresión completa que no contiene un paquete sin expandir. Declaraciones usingEn las declaraciones using , los puntos suspensivos pueden aparecer en la lista de declaradores, lo cual es útil al derivar de un paquete de parámetros de plantilla: template<typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK: B::g and D::g introduced |
(since C++17) |
Indexación de paquetesEn la indexación de paquetes , la expansión del paquete contiene un paquete no expandido seguido de puntos suspensivos y un subíndice. El patrón de la expresión de indexación de paquetes es un identificador , mientras que el patrón del especificador de indexación de paquetes es un nombre de tipo definido . consteval auto first_plus_last(auto... args) { return args...[0] + args...[sizeof...(args) - 1]; } static_assert(first_plus_last(5) == 10); static_assert(first_plus_last(5, 4) == 9); static_assert(first_plus_last(5, 6, 2) == 7); Declaraciones friendEn las declaraciones friend de clase, cada especificador de tipo puede ir seguido de puntos suspensivos: struct C {}; struct E { struct Nested; }; template<class... Ts> class R { friend Ts...; }; template<class... Ts, class... Us> class R<R<Ts...>, R<Us...>> { friend Ts::Nested..., Us...; }; R<C, E> rce; // classes C and E are friends of R<C, E> R<R<E>, R<C, int>> rr; // E::Nested and C are friends of R<R<E>, R<C, int>> Restricciones expandidas mediante plegadoEn las restricciones expandidas mediante plegado , el patrón es la restricción de dicha restricción expandida mediante plegado. Una restricción expandida mediante plegado no se instancia. |
(desde C++26) |
Notas
|
Esta sección está incompleta
Razón: algunas palabras sobre especializaciones parciales y otras formas de acceder a elementos individuales? Mencionar recursión vs logarítmico vs atajos como expresiones de plegado |
| Macro de prueba de características | Valor | Estándar | Característica |
|---|---|---|---|
__cpp_variadic_templates
|
200704L
|
(C++11) | Plantillas variádicas |
__cpp_pack_indexing
|
202311L
|
(C++26) | Indexación de paquetes |
Ejemplo
El siguiente ejemplo define una función similar a
std::printf
, que reemplaza cada ocurrencia del carácter
%
en la cadena de formato con un valor.
La primera sobrecarga se llama 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 la cabeza 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 se vacíe.
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* format) // función base { std::cout << format; } template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) // función variádica recursiva { for (; *format != '\0'; format++) { if (*format == '%') { std::cout << value; tprintf(format + 1, Fargs...); // llamada recursiva return; } std::cout << *format; } } int main() { tprintf("% world% %\n", "Hello", '!', 123); }
Salida:
Hello world! 123
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 1533 | C++11 | una expansión de paquete podía ocurrir en un inicializador de miembro para un miembro | no permitido |
| CWG 2717 | C++11 | las instanciaciones de especificadores de alineación estaban separadas por comas | están separadas por espacios |
Véase también
| Function template | Define una familia de funciones |
| Class template | Define una familia de clases |
sizeof...
|
Consulta el número de elementos en un pack |
| C-style variadic function | Toma un número variable de argumentos |
| Preprocessor macros | También puede ser variádica |
| Fold expression | Reduce un pack sobre un operador binario |
| Pack indexing | Accede al elemento de un pack en un índice especificado |