Erweiterung des Variadic-Vorlagenpakets

Lesezeit: 7 Minuten

Benutzer-Avatar
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).

  • Was, wenn Args war leer?

    – RamblingMad

    5. September 2014 um 7:28 Uhr

Benutzer-Avatar
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)), ...);

  • Eine Variante, die ich gesehen habe, ist using expander = int[]; expander{...}; denn dann hat die Array-Variable keinen Namen und es wird für den Compiler offensichtlich, dass das Array nicht erstellt oder verwendet werden muss.

    – Muhende Ente

    5. September 2014 um 16:20 Uhr

  • Was ist das zweite 0 zum? Der direkt nach dem Komma-Operator

    – Aaron McDaid

    27. Juli 2015 um 21:20 Uhr

  • @AaronMcDaid Damit ist der Typ des gesamten Ausdrucks int und entspricht dem Elementtyp des Arrays.

    – TC

    27. Juli 2015 um 21:22 Uhr

  • Mir ist aufgefallen, dass man an schreiben kann operator void () (ja, verrückt, ich weiß). Aber nein, ich sage das Casting nicht wirklich void ist eine schlechte Idee. Es macht einfach Spaß, über verrückte Dinge nachzudenken, die passieren können, wenn man versucht, ein Paket mit minimalen Nebenwirkungen zu erweitern

    – Aaron McDaid

    27. Juli 2015 um 21:25 Uhr

  • @AaronMcDaid (void) ruft nie auf operator void() (Gott sei Dank).

    – TC

    27. Juli 2015 um 21:26 Uhr


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

  • Verdammt, du hast mich gerade darauf gewettet, dass ich darauf antworte schüttelt die Faust aber Sie sollten Ihrer Antwort wahrscheinlich eine perfekte Weiterleitung hinzufügen. das ist auch ein “primäres Werkzeug in der Box des Variadic-Template-Programmierers”.

    – RamblingMad

    5. September 2014 um 7:26 Uhr

  • Vielen Dank für Ihre Antwort. Ich kenne mich mit der Rekursionsimplementierung aus. Ich möchte nur eine Problemumgehung finden, um Code ohne Rekursion und neue Funktion zu kompilieren.

    – Wjatscheslaw Dronow

    5. September 2014 um 7:26 Uhr

  • @ViacheslavDronov, da Sie Vorlagen verwenden: Sie haben bereits eine Menge Funktionen, die vom Compiler generiert werden. Warum fügen Sie dieser Liste nicht eine hinzu?

    – RamblingMad

    5. September 2014 um 7:27 Uhr

  • @CoffeeandCode Ich erteile Ihnen offiziell die Erlaubnis, meine Antwort zu kopieren und sie durch Hinzufügen und Erklären der perfekten Weiterleitung zu erweitern.

    – Angew ist nicht mehr stolz auf SO

    5. September 2014 um 7:28 Uhr

  • Sie können die Erweiterung einfach mit einem Dummy-Array vornehmen. int dummy[] = { 0, ((void) bar(std::forward<Args>(args)),0)... };.

    – TC

    5. September 2014 um 10:15 Uhr

Benutzer-Avatar
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::forwarddann 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"));

Benutzer-Avatar
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

  • Dies gibt keine Garantien für die Reihenfolge bar wird aufgerufen

    – Erich

    6. Juni 2019 um 18:39 Uhr

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

  • Dies gibt keine Garantien für die Reihenfolge bar wird aufgerufen

    – Erich

    6. Juni 2019 um 18:39 Uhr

1013590cookie-checkErweiterung des Variadic-Vorlagenpakets

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy