Ich versuche, verschiedene Vorlagen und Funktionen zu lernen. Ich kann nicht verstehen, warum dieser Code nicht kompiliert wird:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args)
{
(bar(args)...);
}
int main()
{
foo2(1, 2, 3, "3");
return 0;
}
Wenn ich kompiliere, schlägt es mit dem Fehler fehl:
Fehler C3520: 'args': Das Parameterpaket muss in diesem Zusammenhang erweitert werden
(in Funktion foo2
).
c++
templates
c++11
variadic-templates
Viacheslav Dronov
quelle
quelle
Args
es leer wäre?Antworten:
Einer der Orte, an denen eine Pack-Erweiterung auftreten kann, befindet sich in einer Klammer-Init-Liste . Sie können dies nutzen, indem Sie die Erweiterung in die Initialisierungsliste eines Dummy-Arrays einfügen:
template<typename... Args> static void foo2(Args &&... args) { int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... }; }
So erläutern Sie den Inhalt des Initialisierers ausführlicher:
{ 0, ( (void) bar(std::forward<Args>(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 .
Ein wichtiger Vorteil der Erweiterung
{}
besteht darin, dass eine Bewertung von links nach rechts gewährleistet ist.Mit C ++ 17- fachen Ausdrücken können Sie einfach schreiben
((void) bar(std::forward<Args>(args)), ...);
quelle
using expander = int[]; expander{...};
dass die Array-Variable dann keinen Namen hat und dem Compiler klar wird, dass das Array für mich nicht erstellt oder verwendet werden muss.0
? Der kurz nach dem Komma-Operatorint
dem Elementtyp des Arrays entspricht.operator void ()
(ja, verrückt, ich weiß). Aber nein, ich sage nicht wirklich, dass Castingvoid
eine schlechte Idee ist. Ich habe nur Spaß daran, über verrückte Dinge nachzudenken, die passieren können, wenn versucht wird, eine Packung mit minimalen Nebenwirkungen zu erweitern(void)
ruftoperator void()
(zum Glück) nie auf .Parameterpakete können nur in einer genau definierten Liste von Kontexten erweitert werden, und der Operator
,
ist keiner von ihnen. Mit anderen Worten, es ist nicht möglich, die Pack-Erweiterung zu verwenden, um einen Ausdruck zu generieren, der aus einer Reihe von vom Operator begrenzten Unterausdrücken besteht,
.Als Faustregel gilt: "Durch Erweiterung kann eine Liste mit
,
getrennten Mustern erstellt werden, wobei,
ein Listenbegrenzer vorhanden ist ." Der Operator erstellt,
keine Liste im Sinne der Grammatik.Um eine Funktion für jedes Argument aufzurufen, können Sie die Rekursion verwenden (dies ist das Hauptwerkzeug in der Box des Programmierers für verschiedene Vorlagen):
template <typename T> void bar(T t) {} void foo2() {} template <typename Car, typename... Cdr> void foo2(Car car, Cdr... cdr) { bar(car); foo2(cdr...); } int main() { foo2 (1, 2, 3, "3"); }
Live Beispiel
quelle
int dummy[] = { 0, ((void) bar(std::forward<Args>(args)),0)... };
.SHAMELESS COPY [von der Quelle genehmigt]
Parameterpakete können nur in einer genau definierten Liste von Kontexten erweitert werden, und der Operator
,
ist keiner von ihnen. Mit anderen Worten, es ist nicht möglich, die Pack-Erweiterung zu verwenden, um einen Ausdruck zu generieren, der aus einer Reihe von vom Operator begrenzten Unterausdrücken besteht,
.Als Faustregel gilt: "Durch Erweiterung kann eine Liste mit
,
getrennten Mustern erstellt werden, wobei,
ein Listenbegrenzer vorhanden ist." Der Operator erstellt,
keine Liste im Sinne der Grammatik.Um eine Funktion für jedes Argument aufzurufen, können Sie die Rekursion verwenden (dies ist das Hauptwerkzeug in der Box des Programmierers für verschiedene Vorlagen):
#include <utility> template<typename T> void foo(T &&t){} template<typename Arg0, typename Arg1, typename ... Args> void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){ foo(std::forward<Arg0>(arg0)); foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...); } auto main() -> int{ foo(1, 2, 3, "3"); }
NÜTZLICHE NICHT KOPIERTE INFO
Eine andere Sache, die Sie in dieser Antwort wahrscheinlich nicht gesehen haben, ist die Verwendung des
&&
Bezeichners undstd::forward
. In C ++ kann der&&
Bezeichner eines von zwei Dingen bedeuten: rWertreferenzen oder universelle Referenzen.Ich werde nicht auf r-Wert-Referenzen eingehen, sondern auf jemanden, der mit verschiedenen Vorlagen arbeitet. universelle Referenzen sind ein Geschenk Gottes.
Perfekte Weiterleitung
Eine der Verwendungszwecke
std::forward
und universellen Referenzen ist die perfekte Weiterleitung von Typen an andere Funktionen.Wenn wir in Ihrem Beispiel eine
int&
an übergebenfoo2
, wird dieseint
aufgrund der Signatur der generiertenfoo2
Funktion nach dem Abzug der Vorlage automatisch herabgestuft. Wenn Sie diese dannarg
an eine andere Funktion weiterleiten möchten, die sie durch Referenz ändern würde, erhalten Sie unerwünschte Ergebnisse ( Die Variable wird nicht geändert, dafoo2
ein Verweis auf die temporäre Variable übergeben wird , die durch Übergabe einesint
an sie erstellt wurde. Um dies zu umgehen , geben wir eine Weiterleitungsfunktion an, mit der jede Art von Referenz auf eine Variable (rvalue oder lvalue) verwendet werden kann. Dann, um sicherzugehen, dass wir den genauen Typ übergeben, der in der von uns verwendeten Weiterleitungsfunktion übergeben wurdestd::forward
, dann und nurdann erlauben wir die Herabstufung von Typen; weil wir jetzt an dem Punkt sind, an dem es am wichtigsten ist.Wenn nötig, lesen Sie mehr über universelle Referenzen und perfekte Weiterleitung . Scott Meyers ist eine großartige Ressource.
quelle
Die C ++ 17-Lösung hierfür kommt Ihrem erwarteten Code sehr nahe:
template<typename T> static void bar(T t) {} template<typename... Args> static void foo2(Args... args) { (bar(args), ...); } int main() { foo2(1, 2, 3, "3"); return 0; }
Dadurch wird das Muster mit dem Kommaoperator zwischen den einzelnen Ausdrücken erweitert
// imaginary expanded expression (bar(1), bar(2), bar(3), bar("3"));
quelle
Sie können es
make_tuple
für die Pack-Erweiterung verwenden, da es einen Kontext einführt, in dem,
die von einer Erweiterung erzeugte Sequenz gültig istmake_tuple( (bar(std::forward<Args>(args)), 0)... );
Nun vermute ich, dass das unbenutzte / unbenannte / temporäre Tupel von Nullen, das erzeugt wird, vom Compiler erkannt und weg optimiert werden kann.
Demo
quelle
bar
aufgerufen wirdDies ist ein vollständiges Beispiel, basierend auf den Antworten hier.
Beispiel für die Reproduktion
console.log
in JavaScript:Console console; console.log("bunch", "of", "arguments"); console.warn("or some numbers:", 1, 2, 3); console.error("just a prank", "bro");
Dateiname zB
js_console.h
:#include <iostream> #include <utility> class Console { protected: template <typename T> void log_argument(T t) { std::cout << t << " "; } public: template <typename... Args> void log(Args&&... args) { int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... }; cout << endl; } template <typename... Args> void warn(Args&&... args) { cout << "WARNING: "; int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... }; cout << endl; } template <typename... Args> void error(Args&&... args) { cout << "ERROR: "; int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... }; cout << endl; } };
quelle