Che cosa fa questo codice modello variadic?

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

Di recente è stato pubblicato su isocpp.org senza spiegazione.

La risposta breve è “non funziona molto bene”.

Invoca f su ciascuno degli args... e scarta il valore di ritorno. Ma lo fa in un modo che porta a comportamenti imprevisti in un numero di casi, inutilmente.

Il codice non ha garanzie di ordinamento, e se il valore di ritorno di f per un dato Arg ha un operator, sovraccarico operator, può avere sfortunati effetti collaterali.

Con un po ‘di spazio bianco:

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

Inizieremo dall’interno.

f(std::forward(args)) è un’istruzione incompleta che può essere espansa con un ... Invocherà f su uno degli args quando viene espanso. Chiama questa dichiarazione INVOKE_F .

(INVOKE_F, 0) assume il valore restituito da f(args) , applica operator, quindi 0 . Se il valore di ritorno non ha override, questo scarta il valore di ritorno di f(args) e restituisce uno 0 . Chiama questo INVOKE_F_0 . Se f restituisce un tipo con un operator,(int) sovrascritto operator,(int) , qui accadono cose brutte, e se quell’operatore restituisce un tipo non-POD, è ansible ottenere un comportamento “supportato condizionalmente” in seguito.

[](...){} crea un lambda che accetta le variadiche in stile C come unico argomento. Questo non è lo stesso dei pacchetti di parametri C ++ 11 o dei lambda variadici in C ++ 14. È forse illegale passare tipi non POD-esque in una ... funzione. Chiama questo HELPER

HELPER(INVOKE_F_0...) è un’espansione del pacchetto di parametri. nel contesto di invocare l’ operator() HELPER operator() , che è un contesto legale. La valutazione degli argomenti non è specificata e, a causa della firma di HELPER INVOKE_F_0... probabilmente dovrebbe contenere solo vecchi dati (in linguaggio C ++ 03), o più specificamente [expr.call] / p7 dice: (via @TC )

Il passaggio di un argomento potenzialmente valutato di tipo di class (clausola 9) con un costruttore di copia non banale, un costruttore di movimento non banale o un distruttore non banale, senza parametro corrispondente, è condizionalmente supportato con semantica definita dall’implementazione.

Quindi i problemi di questo codice sono che l’ordine non è specificato e si basa su tipi ben educati o specifiche scelte di implementazione del compilatore.

Possiamo riparare l’ operator, problema come segue:

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

quindi possiamo garantire l’ordine espandendo in un inizializzatore:

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

ma quanto sopra non funziona quando Args... è vuoto, quindi aggiungi un altro 0 :

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

e non c’è alcuna buona ragione per il compilatore di NON eliminare unused[] dall’esistenza, mentre si valuta ancora f su args... in ordine.

La mia variante preferita è:

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

che prende i lambda nulli e li esegue uno alla volta, da sinistra a destra. (Se il compilatore può dimostrare che l’ordine non ha importanza, è comunque libero di eseguirli fuori servizio).

Possiamo quindi implementare quanto sopra con:

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

che mette la “strana espansione” in una funzione isolata ( do_in_order ), e possiamo usarla altrove. Possiamo anche scrivere do_in_any_order che funziona in modo simile, ma rende l’ any_order chiaro: tuttavia, salvo any_order motivi, l’esecuzione del codice in un ordine prevedibile in un’espansione del pacchetto di parametri riduce la sorpresa e riduce al minimo il mal di testa.

Uno svantaggio della tecnica do_in_order è che non tutti i compilatori lo gradiscono – espandere un pacchetto di parametri contenente istruzioni che contengono intere sottovoci non è qualcosa che si aspettano di dover fare.

In realtà chiama function f per ogni argomento in args in ordine non specificato.

 [](...){} 

crea una funzione lambda, che non fa nulla e riceve un numero arbitrario di argomenti (va args).

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

argomento di lambda.

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

chiama f con argomento inoltrato, invia 0 a lambda.

Se vuoi l’ordine specificato puoi usare la seguente cosa :

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