¿Cuáles son las reglas para el token “…” en el contexto de las plantillas variadas?

98

En C ++ 11 hay plantillas variadas como esta:

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args )
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Hay algunas curiosidades sobre esto: la expresión std::forward<Args>(args)...usa ambos Argsy, argspero solo una ...ficha. Además, std::forwardes una función de plantilla no variada que toma solo un parámetro de plantilla y un argumento. ¿Cuáles son las reglas de sintaxis para eso (aproximadamente)? ¿Cómo se puede generalizar?

Además: en la implementación de la función, la elipsis ( ...) está al final de la expresión de interés. ¿Hay alguna razón por la que en la lista de argumentos de la plantilla y en la lista de parámetros la elipsis está en el medio?

Ralph Tandetzky
fuente
2
Brevemente sobre la segunda parte: al "declarar" un paquete de parámetros de plantilla o un paquete de parámetros de función, el ...viene antes del identificador que se introduce. Al usar uno o ambos tipos de paquetes, el ...viene después del patrón de expresión para expandirse.
aschepler

Respuestas:

99

En el contexto de la plantilla variadica, la elipsis ...se usa para descomprimir el paquete de parámetros de la plantilla si aparece en el lado derecho de una expresión (llame a esta expresión patrón por un momento). La regla es que cualquier patrón que esté en el lado izquierdo de ...se repite; los patrones desempaquetados (llámelos expresiones ahora) están separados por comas ,.

Puede entenderse mejor con algunos ejemplos. Suponga que tiene esta plantilla de función:

template<typename ...T>
void f(T ... args) 
{
   g( args... );        //pattern = args
   h( x(args)... );     //pattern = x(args)
   m( y(args...) );     //pattern = args (as argument to y())
   n( z<T>(args)... );  //pattern = z<T>(args)
}

Ahora, si llamo a esta función pasando Tcomo {int, char, short}, entonces cada llamada de función se expande como:

g( arg0, arg1, arg2 );           
h( x(arg0), x(arg1), x(arg2) );
m( y(arg0, arg1, arg2) );
n( z<int>(arg0), z<char>(arg1), z<short>(arg2) );

En el código que publicó, std::forwardsigue el cuarto patrón ilustrado por la n()llamada a la función.

¡Note la diferencia entre x(args)...y y(args...)arriba!


Puede usar ...para inicializar una matriz también como:

struct data_info
{
     boost::any  data;
     std::size_t type_size;
};

std::vector<data_info> v{{args, sizeof(T)}...}; //pattern = {args, sizeof(T)}

que se expande a esto:

std::vector<data_info> v 
{ 
   {arg0, sizeof(int)},
   {arg1, sizeof(char)},
   {arg2, sizeof(short)}
};

Me acabo de dar cuenta de que un patrón podría incluso incluir un especificador de acceso como public, como se muestra en el siguiente ejemplo:

template<typename ... Mixins>
struct mixture : public Mixins ...  //pattern = public Mixins
{
    //code
};

En este ejemplo, el patrón se expande como:

struct mixture__instantiated : public Mixin0, public Mixin1, .. public MixinN  

Es decir, mixturederiva públicamente de todas las clases base.

Espero que ayude.

Nawaz
fuente
1
Respecto a la expresión que se corresponde; Debería ser la expresión más grande posible , ¿no es así? Por ejemplo, x+args...debería ampliarse a x+arg0,x+arg1,x+arg2, no x+arg0,arg1,arg2.
máscara de bits
Por tanto, se ...aplica a todas las entidades expandibles del patrón.
Lightness Races in Orbit
@bitmask: Sí. x+args...debería expandirse a x+arg0,x+arg1,x+arg2, no x+arg0,arg1,arg2 .
Nawaz
1
@ Jarod42: Actualizaré esta respuesta una vez que se lance C ++ 17.
Nawaz
3
@synther: sizeof...(T)no se necesita allí. Simplemente puede escribir:int a[] = { ___ };
Nawaz
48

Lo siguiente fue tomado de la charla "Las plantillas variadas son Funadic" por Andrei Alexandrescu en GoingNative 2012. Puedo recomendarlo para una buena introducción a las plantillas variadas.


Hay dos cosas que se pueden hacer con un paquete variadic. Es posible aplicar sizeof...(vs)para obtener el número de elementos y expandirlo.

Reglas de expansión

Use            Expansion

Ts...          T1, ..., Tn
Ts&&...        T1&&, ..., Tn&&
x<Ts,Y>::z...  x<T1,Y>::z, ..., x<Tn,Y>::z
x<Ts&,Us>...   x<T1&,U1>, ..., x<Tn&,Un>
func(5,vs)...  func(5,v1), ..., func(5,vn)

La expansión procede hacia adentro hacia afuera. Al expandir dos listas en el paso de bloqueo, deben tener el mismo tamaño.

Más ejemplos:

gun(A<Ts...>::hun(vs)...);

Expande todo Tsen la lista de argumentos de la plantilla de Ay luego la función hunse expande con todo vs.

gun(A<Ts...>::hun(vs...));

Expande todo Tsen la lista de argumentos de plantilla de Ay todos vscomo argumentos de función para hun.

gun(A<Ts>::hun(vs)...);

Expande la función huncon Tsy vsen paso de bloqueo.

Nota:

Tsno es un tipo y vsno es un valor. Son alias para una lista de tipos / valores. Cualquiera de las dos listas puede estar potencialmente vacía. Ambos obedecen solo a acciones específicas. Entonces lo siguiente no es posible:

typedef Ts MyList;  // error!
Ts var;             // error!
auto copy = vs;     // error!

Loci de expansión

Argumentos de función

template <typename... Ts>
void fun(Ts... vs)

Listas de inicializadores

any a[] = { vs... };

Especificadores de base

template <typename... Ts>
struct C : Ts... {};
template <typename... Ts>
struct D : Box<Ts>... { /**/ };

Listas de inicializadores de miembros

// Inside struct D
template <typename... Us>
D(Us... vs) : Box<Ts>(vs)... {}

Listas de argumentos temáticos

std::map<Ts...> m;

Solo se compilará si hay una posible coincidencia para los argumentos.

Capturar listas

template <class... Ts> void fun(Ts... vs) {
    auto g = [&vs...] { return gun(vs...); }
    g();
}

Listas de atributos

struct [[ Ts... ]] IAmFromTheFuture {};

Está en la especificación, pero todavía no hay ningún atributo que pueda expresarse como un tipo.

typ1232
fuente
Agradable. Sin embargo, los argumentos de función se dejan fuera de los loci: P
Potatoswatter
@Potatoswatter Gracias. No se mencionó la expansión en la lista de argumentos de la función.
typ1232
@ typ1232 Lo siento por la edición, pero sentí que tu publicación original estaba demasiado cerca del plagio. Por cierto, también vi esa charla hace algún tiempo, increíble.
Walter