Espansione del pacchetto di modelli Variadic

Sto cercando di imparare modelli e funzioni variadici. Non riesco a capire perché questo codice non viene compilato:

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

Quando compilo fallisce con l’errore:

Errore C3520: ‘args’: il pacchetto di parametri deve essere espanso in questo contesto

(nella funzione foo2 ).

Uno dei punti in cui può verificarsi un’espansione di pacchetto si trova all’interno di una lista di inizializzazione rinforzata . È ansible sfruttare questo vantaggio inserendo l’espansione all’interno dell’elenco di inizializzazione di un array fittizio:

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

Per spiegare più dettagliatamente il contenuto dell’inizializzatore:

 { 0, ( (void) bar(std::forward(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 .

Un importante vantaggio dell’espansione in {} è che garantisce una valutazione da sinistra a destra.


Con le espressioni piegate C ++ 1z, puoi semplicemente scrivere

 ((void) bar(std::forward(args)), ...); 

I pacchetti di parametri possono essere espansi solo in un elenco di contesti rigorosamente definito e l’operatore non è uno di questi. In altre parole, non è ansible utilizzare l’espansione del pacchetto per generare un’espressione costituita da una serie di sottoespressioni delimitate dall’operatore.

La regola generale è “L’espansione può generare un elenco di modelli separati, in cui è presente un delimitatore di elenchi .” Operatore , non crea una lista in senso grammaticale.

Per chiamare una funzione per ogni argomento, è ansible utilizzare la ricorsione (che è lo strumento principale nella casella del programmatore del modello variadic):

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

Esempio dal vivo

COPIA SENZA SCHERMO [approvato dalla fonte]

I pacchetti di parametri possono essere espansi solo in un elenco di contesti rigorosamente definito e l’operatore non è uno di questi. In altre parole, non è ansible utilizzare l’espansione del pacchetto per generare un’espressione costituita da una serie di sottoespressioni delimitate dall’operatore.

La regola generale è “L’espansione può generare un elenco di modelli separati, in cui è presente un delimitatore di elenchi.” Operatore , non crea una lista in senso grammaticale.

Per chiamare una funzione per ogni argomento, è ansible utilizzare la ricorsione (che è lo strumento principale nella casella del programmatore del modello variadic):

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

INFO UTILI NON COPIA

Un’altra cosa che probabilmente non hai visto in questa risposta è l’uso di && specificatore e std::forward . In C ++, lo specificatore && può significare una delle 2 cose: riferimenti -valori o riferimenti universali.

Non entrerò nei riferimenti di valore, ma a qualcuno che lavora con modelli variadici; i riferimenti universali sono una trasmissione divina.

Inoltro perfetto

Uno degli usi di std::forward e riferimenti universali è l’inoltro perfetto di tipi ad altre funzioni.

Nel tuo esempio, se passiamo un int& a foo2 verrà automaticamente abbassato di livello a int causa della firma della funzione foo2 generata dopo la deduzione del modello e se si volesse inoltrare questo arg ad un’altra funzione che lo modificasse a piacere, si otterrà risultati indesiderati (la variabile non verrà modificata) perché foo2 passerà un riferimento al temporaneo creato passando ad esso un int . Per ovviare a questo, specifichiamo una funzione di inoltro per prendere qualsiasi tipo di riferimento a una variabile (valore o valore). Quindi, per essere sicuri di passare il tipo esatto passato nella funzione di inoltro, usiamo std::forward , quindi e solo allora consentiamo il declassamento dei tipi; perché ora siamo al punto in cui conta di più.

Se è necessario, leggi di più su riferimenti universali e inoltro perfetto ; scott meyers è piuttosto grande come risorsa.

È ansible utilizzare make_tuple per l’espansione del pacchetto in quanto introduce un contesto in cui la sequenza prodotta da un’espansione è valida

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

Ora, sospetto che la tupla inutilizzata / non nominata / temporanea degli zeri prodotta sia estraibile dal compilatore e ottimizzata via

dimostrazione

L’ordine di esecuzione non è garantito per questo!

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

In questo esempio i parametri verranno stampati in ordine inverso almeno con GCC.

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