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?

Nawaz
quelle
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; });
Hündchen
quelle
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.

R. Martinho Fernandes
quelle
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!

Luc Danton
quelle
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
MSN
quelle
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.

Antidiluvian
quelle
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.

Paul Fultz II
quelle