Espansione con modelli variadici

Qual è la differenza tra le seguenti 3 chiamate per la funzione della gun ?

 template  void fun(Ts... vs) { gun(A::hun(vs)...); gun(A::hun(vs...)); gun(A::hun(vs)...); } 

Sono interessato a una risposta che spiega le tre chiamate utilizzando un esempio specifico.

All’inizio ho semplicemente risposto alla domanda, ma volevo ampliarlo in qualche modo per fornire una spiegazione più completa di come i pacchetti vengono espansi in cosa. Questo è il modo in cui penso comunque alle cose.

Qualsiasi pacchetto immediatamente seguito da un’ellissi è appena stato espanso. Quindi A è equivalente a A e hun(vs...) è analogamente equivalente a hun(v1, v2, ..., vn) . Dove diventa complicato è quando piuttosto che un pacchetto seguito da ellissi si ottiene qualcosa come ((expr)...) . Questo verrà espanso in (expr1, expr2, ..., exprN) dove expri fa riferimento all’espressione originale con qualsiasi pacchetto sostituito con la versione di esso. Quindi se hai hun((vs+1)...) , diventa hun(v1+1, v2+1, ..., vn+1) . Dove diventa più divertente è che expr può contenere più di un pacco (purché abbiano tutti la stessa dimensione!). Questo è il modo in cui implementiamo il modello di inoltro perfetto standard;

 foo(std::forward(args)...) 

Qui expr contiene due pacchetti ( Args e args sono entrambi i pacchetti) e l’espansione “scorre” su entrambi:

 foo(std::forward(arg1), std::forward(arg2), ..., std::forward(argN)); 

Questo ragionamento dovrebbe consentire di esaminare rapidamente i casi per, ad esempio, cosa succede quando chiami foo(1, 2, '3') .

Il primo, gun(A::hun(vs)...); espande Ts “sul posto” e poi c’è un’espressione da espandere per gli ultimi ellissi, quindi questo chiama:

 gun(A::hun(1), A::hun(2), A::hun('3')); 

Il secondo, gun(A::hun(vs...)); espande entrambi i pacchetti in posizione:

 gun(A::hun(1, 2, '3')); 

Il terzo, gun(A::hun(vs)...) , espande entrambi i pacchetti allo stesso tempo:

 gun(A::hun(1), A::hun(2), A::hun('3')); 

[aggiornamento] Per completezza, la gun(A::hun(vs...)...) chiamerebbe:

 gun(A::hun(1, 2, '3'), A::hun(1, 2, '3'), A::hun(1, 2, '3')); 

Infine, c’è un ultimo caso per considerare dove andare in mare sulle ellissi:

 gun(A::hun(vs...)...); 

Questo non verrà compilato. Espandiamo sia Ts che vs “in place”, ma non abbiamo ancora pacchetti da espandere per gli ellissi finali.

Ecco come si espandono quando Ts è T, U e vs is t, u:

 gun(A::hun(vs)...) -> gun(A::hun(t), A::hun(u)) gun(A::hun(vs...)) -> gun(A::hun(t, u)); gun(A::hun(vs)...) -> gun(A::hun(t), A::hun(u)) 

E un altro caso che non hai coperto:

 gun(A::hun(vs...)...) -> gun(A::hun(t, u), A::hun(t, u)) 

Se esegui il codice qui sotto in VS14 otterrai questo risultato:

 calling gun(A::hun(vs)...); struct A::hun(double); struct A::hun(int); gun(struct A, struct A); calling gun(A::hun(vs...)); struct A::hun(int, double); gun(struct A); calling gun(A::hun(vs)...); struct A::hun(double); struct A::hun(int); gun(struct A, struct A); calling gun(A::hun(vs...)...); struct A::hun(int, double); struct A::hun(int, double); gun(struct A, struct A); 

Codice:

 #include  #include  using namespace std; void printTypes() {} template void printTypes(T, Ts... vs) { cout << typeid(T).name() << (sizeof...(Ts) ? ", " : ""); printTypes(vs...); } template struct A { template static auto hun(Us... vs) { cout << " " << typeid(A).name() << "::hun("; printTypes(vs...); cout << ");" << endl; return A{}; } }; template void gun(Ts... vs) { cout << " gun("; printTypes(vs...); cout << ");" << endl; } template void fun(Ts... vs) { cout << "calling gun(A::hun(vs)...);" << endl; gun(A::hun(vs)...); cout << "calling gun(A::hun(vs...));" << endl; gun(A::hun(vs...)); cout << "calling gun(A::hun(vs)...);" << endl; gun(A::hun(vs)...); cout << "calling gun(A::hun(vs...)...);" << endl; gun(A::hun(vs...)...); } int main() { fun(1, 2.0); }