it-swarm.com.de

Variadic Template Pack Erweiterung

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;    
}

Beim Kompilieren schlägt der Fehler fehl:

Fehler C3520: 'args': Das Parameterpaket muss in diesem Zusammenhang erweitert werden

(in Funktion foo2).

66

Eine der Stellen, an denen eine Pack-Erweiterung auftreten kann, befindet sich in einer braced-init-list. Sie können dies nutzen, indem Sie die Erweiterung in die Initialisierungsliste eines Dummy-Arrays aufnehmen:

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

Um den Inhalt des Initialisierers genauer 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 die Auswertung von links nach rechts gewährleistet ist.


Mit C++ 1z fold Ausdrücken können Sie einfach schreiben

((void) bar(std::forward<Args>(args)), ...);
102
T.C.

Parameterpakete können nur in einer streng definierten Liste von Kontexten erweitert werden. Der Operator , gehört nicht dazu. Mit anderen Worten, es ist nicht möglich, die Pack-Erweiterung zu verwenden, um einen Ausdruck zu generieren, der aus einer Reihe von Unterausdrücken besteht, die durch den Operator , begrenzt werden.

Als Faustregel gilt: "Erweiterung kann ein Liste von ,-getrennten Mustern erzeugen, wobei , ein Liste Trennzeichen ist." Der Operator , erstellt keine Liste im Sinne der Grammatik.

Um eine Funktion für jedes Argument aufzurufen, können Sie die Rekursion verwenden (das Hauptwerkzeug in der Box des Variadic Template-Programmierers):

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

37
Angew

SCHAMLOSE KOPIE [von der Quelle genehmigt]

Parameterpakete können nur in einer streng definierten Liste von Kontexten erweitert werden, und der Operator , gehört nicht dazu. Mit anderen Worten, es ist nicht möglich, die Pack-Erweiterung zu verwenden, um einen Ausdruck zu generieren, der aus einer Reihe von Unterausdrücken besteht, die durch den Operator , getrennt werden.

Die Faustregel lautet: "Erweiterung kann eine Liste von ,-getrennten Mustern generieren, wobei , ein Listentrennzeichen ist." Der Operator , erstellt keine Liste im Sinne der Grammatik.

Um eine Funktion für jedes Argument aufzurufen, können Sie die Rekursion verwenden (das Hauptwerkzeug in der Box des Variadic Template-Programmierers):

#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 wahrscheinlich in dieser Antwort nicht gesehen haben, ist die Verwendung des &&-Spezifizierers und std::forward. In C++ kann der &&-Bezeichner eine der folgenden zwei Bedeutungen haben: rvalue-referenzen oder universelle Referenzen.

Ich gehe nicht auf rvalue-Referenzen, sondern auf jemanden, der mit variadischen Vorlagen arbeitet. Universalreferenzen sind eine gute Nachricht.

Perfekte Weiterleitung

Eine der Anwendungen von std::forward und universellen Referenzen ist die perfekte Weitergabe von Typen an andere Funktionen.

Wenn wir in Ihrem Beispiel einen int& an foo2 übergeben, wird er aufgrund der Signatur der generierten foo2-Funktion nach dem Vorlagen-Abzug automatisch auf int herabgestuft. Wenn Sie diese arg dann an eine andere Funktion weiterleiten möchten, die sie durch Verweis ändern würde führt zu unerwünschten Ergebnissen (die Variable wird nicht geändert), da foo2 einen Verweis auf das temporäre Objekt weitergibt, das durch Übergeben einer int erstellt wird. Um dies zu umgehen, geben wir eine Weiterleitungsfunktion an, um any - Typ von reference auf eine Variable (rvalue oder lvalue) zu übertragen. Um sicherzugehen, dass wir den genauen Typ übergeben, der in der Weiterleitungsfunktion übergeben wird, verwenden wir std::forward. Dann und only erlauben wir das Herabstufen von Typen. weil wir jetzt an dem Punkt sind, an dem es am wichtigsten ist.

Wenn Sie möchten, lesen Sie weitere Informationen zu Universalreferenzen und Perfect Forwarding ; Scott Meyers ist eine großartige Ressource.

15
CoffeeandCode

Sie können make_Tuple für die Packungserweiterung verwenden, da dadurch ein Kontext eingeführt wird, in dem die von einer Erweiterung erzeugte ,-Sequenz gültig ist

make_Tuple( (bar(std::forward<Args>(args)), 0)... );

Nun vermute ich, dass das unbenutzte/unbenannte/temporäre Tuple von Nullen vom Compiler erkannt und wegoptimiert wird.

Demo

2
Lorah Attkins

Die C++ 17-Lösung dafür liegt sehr nahe an Ihrem erwarteten Code:

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 jedem Ausdruck erweitert

// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));
1

Dies ist ein vollständiges Beispiel, basierend auf den Antworten hier.

Beispiel zum Reproduzieren von 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, z. 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;
    }
};
0
lama12345