¿Qué hace este código de plantilla variadic?

template  void for_each_argument(F f, Args&&... args) { [](...){}((f(std::forward(args)), 0)...); } 

Recientemente apareció en isocpp.org sin explicación.

    La respuesta corta es “no lo hace muy bien”.

    Invoca f en cada uno de los args... , y descarta el valor de retorno. Pero lo hace de una manera que conduce a un comportamiento inesperado en varios casos, innecesariamente.

    El código no tiene garantías de pedido, y si el valor de retorno de f para un Arg dado tiene un operator, sobrecargado operator, puede tener efectos secundarios desafortunados.

    Con un poco de espacio en blanco:

     [](...){}( ( f(std::forward(args)), 0 )... ); 

    Comenzaremos desde adentro.

    f(std::forward(args)) es una instrucción incompleta que se puede expandir con ... Invocará f en uno de los args cuando se expande. Llame a esta statement INVOKE_F .

    (INVOKE_F, 0) toma el valor de retorno de f(args) , aplica operator, luego 0 . Si el valor devuelto no tiene anulaciones, descarta el valor de retorno de f(args) y devuelve un 0 . Llamar a esto INVOKE_F_0 . Si f devuelve un tipo con un operator,(int) reemplazado operator,(int) , aquí suceden cosas malas, y si ese operador devuelve un tipo que no sea POD-esque, más adelante podrá obtener un comportamiento “condicionalmente compatible”.

    [](...){} crea una lambda que toma variadicas estilo C como su único argumento. Esto no es lo mismo que los paquetes de parámetros C ++ 11, o Códigos 14 variantes lambdas. Posiblemente sea ilegal pasar tipos que no sean POD-esque a una ... función. Llamar a este HELPER

    HELPER(INVOKE_F_0...) es una expansión del paquete de parámetros. en el contexto de invocar al operator() HELPER operator() , que es un contexto legal. La evaluación de argumentos no está especificada, y debido a la firma de INVOKE_F_0... probablemente solo debería contener datos simples (en el lenguaje C ++ 03), o más específicamente [expr.call] / p7 dice: (a través de @TC) )

    Al pasar un argumento potencialmente evaluado de tipo de clase (Cláusula 9) que tiene un constructor de copia no trivial, un constructor de movimiento no trivial o un destructor no trivial, sin el parámetro correspondiente, se admite condicionalmente con semántica definida por la implementación.

    Entonces, el problema de este código es que el orden no está especificado y depende de los tipos que se comportan bien o de las elecciones de implementación del comstackdor específicas.

    Podemos solucionar el problema del operator, siguiente manera:

     template  void for_each_argument(F f, Args&&... args) { [](...){}((void(f(std::forward(args))), 0)...); } 

    entonces podemos garantizar el pedido expandiendo en un inicializador:

     template  void for_each_argument(F f, Args&&... args) { int unused[] = {(void(f(std::forward(args))), 0)...}; void(unused); // suppresses warnings } 

    pero lo anterior falla cuando Args... está vacío, así que agrega otro 0 :

     template  void for_each_argument(F f, Args&&... args) { int unused[] = {0, (void(f(std::forward(args))), 0)...}; void(unused); // suppresses warnings } 

    y no hay una buena razón para que el comstackdor NO elimine unused[] de la existencia no unused[] , mientras se sigue evaluando f en args... en orden.

    Mi variante preferida es:

     template  void do_in_order(F&&... f) { int unused[] = {0, (void(std::forward(f)()), 0)...}; void(unused); // suppresses warnings } 

    que toma nullary lambdas y las ejecuta de a una por vez, de izquierda a derecha. (Si el comstackdor puede demostrar que el orden no importa, es libre de ejecutarlos fuera de orden, sin embargo).

    Luego podemos implementar lo anterior con:

     template  void for_each_argument(F f, Args&&... args) { do_in_order( [&]{ f(std::forward(args)); }... ); } 

    que pone la “expansión extraña” en una función aislada ( do_in_order ), y podemos usarla en otra parte. También podemos escribir do_in_any_order que funciona de manera similar, pero deja claro el any_order : sin embargo, salvo razones extremas, tener código ejecutado en un orden predecible en una expansión de paquete de parámetros reduce la sorpresa y reduce al mínimo los dolores de cabeza.

    Una desventaja de la técnica do_in_order es que no a todos los comstackdores les gusta, expandir un paquete de parámetros que contenga una statement que contenga sub-declaraciones completas no es algo que esperen tener que hacer.

    En realidad, llama a la función f para cada argumento en args en un orden no especificado.

     [](...){} 

    crea la función lambda, que no hace nada y recibe un número arbitrario de argumentos (va args).

     ((f(std::forward(args)), 0)...) 

    argumento de lambda.

     (f(std::forward(args)), 0) 

    llame a f con argumento reenviado, envíe 0 a lambda.

    Si quieres un orden específico, puedes usar lo siguiente :

     using swallow = int[]; (void)swallow{0, (f(std::forward(args)), 0)...};