Warum kann ich in C ++ 11 keinen Lambdas-Vektor (desselben Typs) erstellen?


87

Ich habe versucht, einen Lambda-Vektor zu erstellen, bin aber gescheitert:

auto ignore = [&]() { return 10; };  //1
std::vector<decltype(ignore)> v;     //2
v.push_back([&]() { return 100; });  //3

Bis zu Zeile 2 wird es gut kompiliert . Die Zeile 3 gibt jedoch einen Kompilierungsfehler aus :

Fehler: Keine Übereinstimmungsfunktion für den Aufruf von 'std :: vector <main () :: <lambda () >> :: push_back (main () :: <lambda ()>)'

Ich möchte keinen Vektor von Funktionszeigern oder einen Vektor von Funktionsobjekten. Ein Vektor von Funktionsobjekten, die echte Lambda-Ausdrücke einkapseln , würde jedoch für mich funktionieren. Ist das möglich?


23
"Ich möchte keinen Vektor von Funktionszeigern oder einen Vektor von Funktionsobjekten." Aber darum haben Sie gebeten. Ein Lambda ist ein Funktionsobjekt.
Nicol Bolas

Antworten:


134

Jedes Lambda hat einen anderen Typ - auch wenn es dieselbe Signatur hat. Sie müssen einen Laufzeit-Kapselungscontainer verwenden, z. B. std::functionwenn Sie so etwas tun möchten.

z.B:

std::vector<std::function<int()>> functors;
functors.push_back([&] { return 100; });
functors.push_back([&] { return  10; });

52
Das Management eines hundertköpfigen Entwicklerteams klingt für mich eher nach einem Albtraum :)
Jeremy Friesner

10
Vergessen Sie auch nicht, dass Captureless Lambdas ([] -Stil) zu Funktionszeigern werden können. So konnte er ein Array von Funktionszeigern des gleichen Typs speichern. Beachten Sie, dass VC10 dies noch nicht implementiert.
Nicol Bolas

Sollte in diesen Beispielen übrigens nicht Capture-less verwendet werden? Oder ist es notwendig? - Übrigens scheint Captureless Lambda to Function Pointer in VC11 unterstützt zu werden. Habe es aber nicht getestet.
Klaim

2
Ist es möglich, Vektorspeicherfunktionen unterschiedlichen Typs zu erstellen? Könnte std::function<int()ich, anstatt es zu beschränken , verschiedene Funktionsprototypen verwenden?
Manatttta

2
@manatttta Was wäre der Punkt? Es gibt Container, in denen Objekte des gleichen Typs gespeichert, zusammen organisiert und bearbeitet werden können. Sie können auch fragen: "Kann ich eine vectorSpeicherung für beide erstellen std::functionund std::string?" Und die Antwort ist dieselbe: Nein, denn das ist nicht die beabsichtigte Verwendung. Sie können eine Klasse im Stil einer Variante verwenden, um eine ausreichende Typlöschung durchzuführen, um unterschiedliche Elemente in einen Container einzufügen, und gleichzeitig eine Methode einschließen, mit der ein Benutzer den Typ "real" bestimmen und damit auswählen kann, was zu tun ist (z. B. wie aufgerufen werden soll). jedes Element ... aber warum noch einmal so weit gehen? Gibt es eine echte Begründung?
underscore_d

40

Alle Lambda-Ausdrücke haben einen anderen Typ, auch wenn sie zeichenweise identisch sind . Sie schieben ein Lambda eines anderen Typs (weil es ein anderer Ausdruck ist) in den Vektor, und das funktioniert offensichtlich nicht.

Eine Lösung besteht darin, std::function<int()>stattdessen einen Vektor zu erstellen.

auto ignore = [&]() { return 10; };
std::vector<std::function<int()>> v;
v.push_back(ignore);
v.push_back([&]() { return 100; });

Außerdem ist es keine gute Idee, sie zu verwenden, [&]wenn Sie nichts erfassen.


14
()Benötigen Sie keine Lambdas, die keine Argumente enthalten.
Welpe

18

Während das, was andere gesagt haben, relevant ist, ist es immer noch möglich, einen Lambda-Vektor zu deklarieren und zu verwenden, obwohl dies nicht sehr nützlich ist:

auto lambda = [] { return 10; };
std::vector<decltype(lambda)> vec;
vec.push_back(lambda);

Sie können also eine beliebige Anzahl von Lambdas darin aufbewahren, solange es sich um eine Kopie / Bewegung handelt lambda!


Was tatsächlich nützlich sein könnte, wenn der Pushback in einer Schleife mit unterschiedlichen Parametern erfolgt. Vermutlich zu faulen Bewertungszwecken.
MaHuJa

7
Nein, Sie setzen nicht die Parameter in den Vektor, sondern nur das Funktionsobjekt. Es wäre also ein Vektor mit allen Kopien desselben Lambda
hariseldon78

16

Wenn Ihr Lambda zustandslos ist, dh in [](...){...}C ++ 11, kann es in einen Funktionszeiger umgewandelt werden. Theoretisch könnte ein C ++ 11-kompatibler Compiler dies kompilieren:

auto ignore = []() { return 10; };  //1 note misssing & in []!
std::vector<int (*)()> v;     //2
v.push_back([]() { return 100; });  //3

4
Für das Protokoll auto ignore = *[] { return 10; };würde ignoreein int(*)().
Luc Danton

1
@ Luc, oh das ist eklig! Wann haben sie das hinzugefügt?
MSN

3
Nun, da die Konvertierungsfunktion, die es überhaupt erlaubt, einen Funktionszeiger zu verwenden, nicht expliciterforderlich ist, ist die Dereferenzierung eines Lambda-Ausdrucks gültig und dereferenziert den aus der Konvertierung resultierenden Zeiger. Verwenden Sie dann autoZerfälle, die auf einen Zeiger verweisen. (Verwenden auto&oder auto&&hätte die Referenz behalten.)
Luc Danton

Ah ... Dereferenzieren des resultierenden Zeigers. Das macht Sinn. War das Fehlen ()beabsichtigt oder versehentlich?
MSN

Absichtlich ist der Lambda-Ausdruck äquivalent (aber zwei Zeichen kürzer).
Luc Danton

6

Sie könnten eine Lambda-Generierungsfunktion verwenden (aktualisiert mit dem von Nawaz vorgeschlagenen Fix):

#include <vector>
#include <iostream>

int main() {
    auto lambda_gen = [] (int i) {return [i](int x){ return i*x;};} ;

    using my_lambda = decltype(lambda_gen(1));

    std::vector<my_lambda> vec;

    for(int i = 0; i < 10; i++) vec.push_back(lambda_gen(i));

    int i = 0;

    for (auto& lambda : vec){
        std::cout << lambda(i) << std::endl;
        i++;
    }
}

Aber ich denke, Sie haben zu diesem Zeitpunkt im Grunde Ihre eigene Klasse gemacht. Andernfalls müssen Sie wahrscheinlich ein Tupel verwenden, wenn die Lambdas völlig unterschiedliche Caputres / Args usw. haben.


Gute Idee, es in eine Funktion zu verpacken, wie lambda_gensie wiederum ein Lambda selbst sein kann. Allerdings auto a = lambda_gen(1);macht einen unnötigen Anruf, die vermieden werden können , wenn wir dies schreiben decltype(lambda_gen(1)).
Nawaz

Macht das nicht noch einen zusätzlichen Anruf? Ein weiterer kleiner Punkt ist, dass die Frage C ++ 11 angibt, sodass man der Funktion, die ich denke, einen nachgestellten Rückgabetyp hinzufügen müsste.
vorsintflutlich

Nein. Alles, was sich darin decltype befindet , wird nicht bewertet , sodass der Anruf nicht tatsächlich getätigt wird. So ist es auch mit sizeof. Außerdem funktioniert dieser Code in C ++ 11 nicht, selbst wenn Sie einen nachfolgenden Rückgabetyp hinzufügen !!
Nawaz

4

Jedes Lambda ist ein anderer Typ. Sie müssen std::tupleanstelle von verwenden std::vector.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.