Wie ruft man eine function für alle variadischen Vorlagenargumente auf?

ich möchte zu tun

template void print(ArgTypes... Args) { print(Args)...; } 

Und muss es dieser ziemlich sperrigen rekursiven Kette entsprechen:

 template void print(const T& t, ArgTypes... Args) { print(t); print(Args...); } 

gefolgt von expliziten Ein-Parameter-Spezialisierungen für jeden Typ, den ich drucken möchte.

Das “Problem” bei der rekursiven Implementierung besteht darin, dass viel redundanter Code erzeugt wird, da jeder rekursive Schritt zu einer neuen function von N-1 Argumenten führt, während der Code, den ich gerne hätte, nur Code für ein einzelnes N erzeugen würde -arg print , und haben höchstens N spezialisierte print .

Der typische Ansatz besteht darin, einen dummen list-initialisierer zu verwenden und die Erweiterung darin auszuführen:

 { print(Args)... } 

Die Bewertungsreihenfolge wird in lockigen Initialisierern von links nach rechts gewährleistet.

Aber der print kehrt zurück void also müssen wir das umgehen. Machen wir es dann zu einem Int.

 { (print(Args), 0)... } 

Dies funktioniert jedoch nicht direkt als statement. Wir müssen ihm einen Typ geben.

 using expand_type = int[]; expand_type{ (print(Args), 0)... }; 

Dies funktioniert, solange immer ein Element im Args Paket vorhanden ist. Zero-Size-Arrays sind nicht gültig, aber wir können das umgehen, indem wir immer mindestens ein Element haben.

 expand_type{ 0, (print(Args), 0)... }; 

Wir können dieses Muster mit einem Makro wiederverwendbar machen.

 namespace so { using expand_type = int[]; } #define SO_EXPAND_SIDE_EFFECTS(PATTERN) ::so::expand_type{ 0, ((PATTERN), 0)... } // usage SO_EXPAND_SIDE_EFFECTS(print(Args)); 

Allerdings macht das Wiederverwendbarmachen ein wenig mehr Aufmerksamkeit für einige Details erforderlich. Wir wollen hier keine überladenen Komma-Operatoren verwenden. Komma kann nicht mit einem der Argumente void überladen void , also nutzen wir das aus.

 #define SO_EXPAND_SIDE_EFFECTS(PATTERN) \ ::so::expand_type{ 0, ((PATTERN), void(), 0)... } 

Wenn Sie paranoid sind, vor dem Compiler zu fürchten, der große Nullen-Arrays für nichts zuweist, können Sie einen anderen Typ verwenden, der auf diese Weise list-initialisiert werden kann, aber nichts speichert.

 namespace so { struct expand_type { template  expand_type(T&&...) {} }; } 

C ++ 17-facher Ausdruck:

 (f(args), ...); 

Behalte einfache Dinge einfach 😉

Wenn Sie etwas aufrufen, das ein Objekt mit einem überladenen Komma-Operator zurückgibt, verwenden Sie Folgendes:

 ((void)f(args), ...); 

Sie können einen noch einfacheren und lesbareren Ansatz verwenden

 template void print(ArgTypes... Args) { for (const auto& arg : {Args...}) { print(arg); } } 

Ich habe mit beiden Varianten auf Compile Explorer gespielt und sowohl gcc als auch clang mit O3 oder O2 produzieren genau den gleichen Code, aber meine Variante ist offensichtlich sauberer.