c++ templates c++11 variadic-templates

c++ - Expansión del paquete de plantillas variables



templates c++11 (5)

¡La orden de ejecución no está garantizada para esto!

make_tuple( (bar(std::forward<Args>(args)), 0)... );

En este ejemplo, los parámetros se imprimirán en orden inverso al menos con GCC.

foo2(1, 2, 3, "3"); calling bar for 3 calling bar for 3 calling bar for 2 calling bar for 1

Estoy tratando de aprender plantillas y funciones variadas. No puedo entender por qué este código no se compila:

template<typename T> static void bar(T t) {} template<typename... Args> static void foo2(Args... args) { (bar(args)...); } int main() { foo2(1, 2, 3, "3"); return 0; }

Cuando compilo falla con el error:

Error C3520: ''args'': el paquete de parámetros debe expandirse en este contexto

(en la función foo2 ).


Los paquetes de parámetros solo se pueden expandir en una lista de contextos estrictamente definida, y el operador , no es uno de ellos. En otras palabras, no es posible usar la expansión del paquete para generar una expresión que consiste en una serie de subexpresiones delimitadas por el operador,.

La regla de oro es "Expansión puede generar una lista de patrones separados, donde , es un delimitador de lista ". Operador , no construye una lista en el sentido gramatical.

Para llamar a una función para cada argumento, puede usar recursión (que es la herramienta principal en el cuadro del programador de plantillas variadic):

template <typename T> void bar(T t) {} void foo2() {} template <typename Car, typename... Cdr> void foo2(Car car, Cdr... cdr) { bar(car); foo2(cdr...); } int main() { foo2 (1, 2, 3, "3"); }

Ejemplo en vivo


Puede usar make_tuple para la expansión del paquete ya que introduce un contexto donde la secuencia producida por una expansión es válida

make_tuple( (bar(std::forward<Args>(args)), 0)... );

Ahora, sospecho que la tupla de ceros no utilizada / sin nombre / temporal que se produce es detectable por el compilador y optimizada

Demo


Uno de los lugares donde puede producirse la expansión de un paquete está dentro de una lista inicial arrinconada . Puede aprovechar esto colocando la expansión dentro de la lista de inicialización de una matriz ficticia:

template<typename... Args> static void foo2(Args &&... args) { int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... }; }

Para explicar el contenido del inicializador con más detalle:

{ 0, ( (void) bar(std::forward<Args>(args)), 0) ... }; | | | | | | | | | --- pack expand the whole thing | | | | | | --perfect forwarding --- comma operator | | | -- cast to void to ensure that regardless of bar()''s return type | the built-in comma operator is used rather than an overloaded one | ---ensure that the array has at least one element so that we don''t try to make an illegal 0-length array when args is empty

Demo

Una ventaja importante de la expansión en {} es que garantiza la evaluación de izquierda a derecha.

Con las expresiones C ++ 1z fold , puedes escribir

((void) bar(std::forward<Args>(args)), ...);


SHAMELESS COPY [aprobado por su fuente]

Los paquetes de parámetros solo se pueden expandir en una lista de contextos estrictamente definida, y el operador , no es uno de ellos. En otras palabras, no es posible usar la expansión del paquete para generar una expresión que consiste en una serie de subexpresiones delimitadas por el operador,.

La regla de oro es "Expansión puede generar una lista de patrones separados, donde , es un delimitador de lista". Operador , no construye una lista en el sentido gramatical.

Para llamar a una función para cada argumento, puede usar recursión (que es la herramienta principal en el cuadro del programador de plantillas variadic):

#include <utility> template<typename T> void foo(T &&t){} template<typename Arg0, typename Arg1, typename ... Args> void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){ foo(std::forward<Arg0>(arg0)); foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...); } auto main() -> int{ foo(1, 2, 3, "3"); }

INFORMACIÓN ÚTIL NO COPIADA

Otra cosa que probablemente no haya visto en esta respuesta es el uso del especificador && y std::forward . En C ++, el especificador && puede significar una de dos cosas: referencias-rvalue, o referencias universales.

No entraré en referencias rvalue, sino a alguien que trabaje con plantillas variadas; las referencias universales son un envío de Dios.

Reenvío perfecto

Uno de los usos de las referencias std::forward y universal es el reenvío perfecto de tipos a otras funciones.

En su ejemplo, si pasamos un int& a foo2 , se degradará automáticamente a int debido a la firma de la función foo2 generada después de la deducción de la plantilla y si usted desea entonces reenviar este arg a otra función que lo modificaría a su referencia, usted obtendrá resultados no deseados (la variable no se cambiará) porque foo2 pasará una referencia al temporal creado pasandole un int . Para evitar esto, especificamos una función de reenvío para tomar cualquier tipo de referencia a una variable (rvalue o lvalue). Luego, para estar seguros de que pasamos el tipo exacto pasado en la función de reenvío, usamos std::forward , entonces y solo entonces permitimos la degradación de los tipos; porque ahora estamos en el punto donde más importa.

Si es necesario, lea más sobre referencias universales y reenvío perfecto ; scott meyers es bastante bueno como recurso.