Namespaces
Variants

Pack (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Template specialization
Parameter packs (C++11)
Miscellaneous

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,
  • el número de elementos en la expansión del paquete de su inicializador, si el paquete es un paquete de captura de inicialización lambda,
(since C++20)
  • tamaño del inicializador del enlace estructurado menos el número de elementos no-pack en la declaración del enlace estructurado, si el pack es un pack de enlace estructurado.
(since C++26)

Una plantilla con al menos un paquete de parámetros se denomina plantilla variádica .

Contenidos

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)
1) Un paquete de parámetros de plantilla constante con un nombre opcional
2) Un parameter pack de tipo template con un nombre opcional
(since C++20)
4) Un parámetro de plantilla de plantilla empaquetado con un nombre opcional
5) Un paquete de parámetros de función con un nombre opcional
6) Expansión de paquete: se expande a una lista de cero o más 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 excepciones

La 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 plegado

En las expresiones de plegado , el patrón es la subexpresión completa que no contiene un paquete sin expandir.

Declaraciones using

En 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 paquetes

En 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 friend

En 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 plegado

En 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

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