
Wjatscheslaw Dronow
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’: Parameterpaket muss in diesem Zusammenhang erweitert werden
(in Funktion foo2
).

TC
Einer der Orte, an denen eine Paketerweiterung stattfinden kann, ist innerhalb von a geklammerte Init-Liste. Sie können dies ausnutzen, 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) ... };
}
Um den Inhalt des Initialisierers näher zu erläutern:
{ 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 in {}
ist, dass es eine Links-nach-Rechts-Auswertung garantiert.
Mit C++17 Ausdrücke faltendu kannst einfach schreiben
((void) bar(std::forward<Args>(args)), ...);
Parameterpakete können nur in einer streng definierten Liste von Kontexten und Operatoren erweitert werden ,
ist keiner von ihnen. Mit anderen Worten, es ist nicht möglich, die Paketerweiterung zu verwenden, um einen Ausdruck zu generieren, der aus einer Reihe von Unterausdrücken besteht, die durch Operatoren getrennt sind ,
.
Die Faustregel lautet: „Expansion kann a generieren aufführen von ,
-getrennte Muster wo ,
ist ein aufführen Trennzeichen.” Operator ,
erstellt keine Liste im Sinne der Grammatik.
Um eine Funktion für jedes Argument aufzurufen, können Sie die Rekursion verwenden (das wichtigste Werkzeug in der Box des Variadic-Vorlagenprogrammierers):
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

RamblingMad
SCHAMLOSE KOPIE [approved by its source]
Parameterpakete können nur in einer streng definierten Liste von Kontexten und Operatoren erweitert werden ,
ist keiner von ihnen. Mit anderen Worten, es ist nicht möglich, die Paketerweiterung zu verwenden, um einen Ausdruck zu generieren, der aus einer Reihe von Unterausdrücken besteht, die durch Operatoren getrennt sind ,
.
Die Faustregel lautet: „Expansion kann eine Liste von generieren ,
-getrennte Muster wo ,
ist ein Listentrennzeichen.” Operator ,
erstellt keine Liste im Sinne der Grammatik.
Um eine Funktion für jedes Argument aufzurufen, können Sie die Rekursion verwenden (das wichtigste Werkzeug in der Box des Variadic-Vorlagenprogrammierers):
#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 INFORMATIONEN
Eine andere Sache, die Sie in dieser Antwort wahrscheinlich nicht gesehen haben, ist die Verwendung von &&
Spezifizierer und std::forward
. In C++ ist die &&
Bezeichner kann eines von zwei Dingen bedeuten: rvalue-Referenzen oder universelle Referenzen.
Ich werde nicht auf rvalue-Referenzen eingehen, sondern auf jemanden, der mit variadischen Vorlagen arbeitet; universelle Referenzen sind ein Geschenk Gottes.
Perfekte Weiterleitung
Eine der Verwendungen von std::forward
und universelle Referenzen sind eine perfekte Weiterleitung von Typen an andere Funktionen.
Wenn wir in Ihrem Beispiel eine übergeben int&
zu foo2
es wird automatisch herabgestuft auf int
wegen der Signatur der generierten foo2
Funktion nach Vorlagenabzug und wenn du wolltest dann diese weiterleiten arg
zu einer anderen Funktion, die sie durch Referenz ändern würde, erhalten Sie unerwünschte Ergebnisse (die Variable wird nicht geändert), weil foo2
wird einen Verweis auf das temporäre übergeben, das durch Übergeben von an erstellt wurde int
dazu. Um dies zu umgehen, geben wir eine Weiterleitungsfunktion an irgendein eine Art von Hinweis in eine Variable (rvalue oder lWert). Dann, um sicherzustellen, dass wir genau den Typ übergeben, der in der von uns verwendeten Weiterleitungsfunktion übergeben wird std::forward
dann und nur erlauben wir dann die Herabstufung von Typen; weil wir jetzt an dem Punkt sind, wo es am wichtigsten ist.
Lesen Sie bei Bedarf weiter universelle Referenzen und perfekte Weiterleitung; Scott Meyers ist als Ressource ziemlich großartig.
Die C++17-Lösung dafü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 Komma-Operator zwischen jedem Ausdruck erweitert
// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));

Lorah Attkins
Sie können verwenden make_tuple
für die Paketerweiterung, da es einen Kontext einführt, in dem die ,
Folge, die durch eine Erweiterung erzeugt wird, ist gültig
make_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 wegoptimiert wird.
Demo

kungfooman
Dies ist ein vollständiges Beispiel, basierend auf den Antworten hier.
Beispiel zum Nachmachen console.log
wie in JavaScript gesehen:
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;
}
};
10135900cookie-checkErweiterung des Variadic-Vorlagenpaketsyes
Was, wenn
Args
war leer?– RamblingMad
5. September 2014 um 7:28 Uhr