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
è 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)...};