Funktion, die einen Lambda-Ausdruck zurückgibt

88

Ich frage mich, ob es möglich ist, eine Funktion zu schreiben, die eine Lambda-Funktion in C ++ 11 zurückgibt. Ein Problem ist natürlich, wie eine solche Funktion deklariert wird. Jedes Lambda hat einen Typ, aber dieser Typ ist in C ++ nicht ausdrückbar. Ich denke nicht, dass das funktionieren würde:

auto retFun() -> decltype ([](int x) -> int)
{
    return [](int x) { return x; }
}

Noch dies:

int(int) retFun();

Mir sind keine automatischen Konvertierungen von Lambdas in Zeiger auf Funktionen oder ähnliches bekannt. Ist die einzige Lösung, die ein Funktionsobjekt handgefertigt und zurückgibt?

Bartosz Milewski
quelle
1
Um das bereits Gesagte hinzuzufügen, können zustandslose Lambda-Funktionen in Funktionszeiger konvertiert werden.
snk_kid
3
IMO Ihre erste Option wird nicht funktionieren, da das Lambda in der decltypenicht das gleiche wie im Funktionskörper ist und daher einen anderen Typ hat (auch wenn Sie die return-Anweisung enthalten haben)
Motti
1
Wenn ein Lambda eine leere Erfassungsklausel hat, kann es übrigens implizit in einen Zeiger auf Funktion konvertiert werden.
GManNickG
@GMan: Es sei denn, Sie verwenden Visual C ++ 2010 oder eine Version von g ++, die vor mehr als einem Jahr (oder so ungefähr) veröffentlicht wurde. Die implizite Konvertierung von Captureless-Lambda in Funktionszeiger wurde erst im März 2010 in N3092 hinzugefügt.
James McNellis
4
Lambda-Ausdrücke können im Allgemeinen nicht in nicht bewerteten Operanden vorkommen. Also decltype([](){})oder sizeof([]() {})ist schlecht geformt, egal wo du es schreibst.
Johannes Schaub - litb

Antworten:

98

Sie benötigen kein handgefertigtes Funktionsobjekt, sondern verwenden es einfach std::function , in das Lambda-Funktionen konvertierbar sind:

Dieses Beispiel gibt die Ganzzahlidentitätsfunktion zurück:

std::function<int (int)> retFun() {
    return [](int x) { return x; };
}
Sean
quelle
6
Duh! Ich liebe StackOverflow. Ich hätte viel länger gebraucht, um das Thema zu recherchieren und dann die Antwort auf StackOverflow zu erhalten. Danke Sean!
Bartosz Milewski
5
Dies führt jedoch zu einer Speicherzuweisung im Konstruktor von std::function.
Maxim Egorushkin
2
@Maxim Yegorushkin std :: function verfügt über eine Verschiebungssemantik und kann benutzerdefinierte Allokatoren verwenden. Der C ++ 0x-Arbeitsentwurf enthält folgende Hinweise: "[Hinweis: Implementierungen werden empfohlen, um die Verwendung von dynamisch zugewiesenem Speicher für kleine aufrufbare Objekte zu vermeiden , wobei das Ziel von f ein Objekt ist, das nur einen Zeiger oder eine Referenz auf ein Objekt und einen Elementfunktionszeiger enthält. —end note] "Sie können also im Grunde nicht viele Annahmen darüber treffen, welche Zuordnungsstrategie eine bestimmte Implementierung verwendet, aber Sie sollten in der Lage sein um trotzdem deine eigenen (gepoolten) Allokatoren zu verwenden.
snk_kid
1
@ Maxim: Meine Antwort war auf die Frage "Ist die einzige Lösung, die ein Funktionsobjekt handgefertigt und zurückgegeben hat?"
Sean
2
Denken Sie daran, dass std::functionTyplöschung verwendet wird, was die Kosten für einen virtuellen Funktionsaufruf beim Aufrufen von bedeuten kann std::function. Beachten Sie, ob die zurückgegebene Funktion in einer engen inneren Schleife oder in einem anderen Kontext verwendet wird, in dem die leichte Ineffizienz von Bedeutung ist.
Anthony Hall
29

Für dieses einfache Beispiel brauchen Sie nicht std::function.

Aus dem Standard §5.1.2 / 6:

Der Abschlusstyp für einen Lambda-Ausdruck ohne Lambda-Erfassung verfügt über eine öffentliche nicht virtuelle nicht explizite const-Konvertierungsfunktion für den Zeiger auf eine Funktion mit denselben Parameter- und Rückgabetypen wie der Funktionsaufrufoperator des Abschlusstyps. Der von dieser Konvertierungsfunktion zurückgegebene Wert ist die Adresse einer Funktion, die beim Aufrufen den gleichen Effekt hat wie das Aufrufen des Funktionsaufrufoperators des Schließungstyps.

Da Ihre Funktion keine Erfassung hat, bedeutet dies, dass das Lambda in einen Zeiger auf eine Funktion vom Typ konvertiert werden kann int (*)(int):

typedef int (*identity_t)(int); // works with gcc
identity_t retFun() { 
  return [](int x) { return x; };
}

Das ist mein Verständnis, korrigiere mich, wenn ich falsch liege.

user534498
quelle
1
Das klingt richtig. Leider funktioniert es nicht mit dem aktuellen Compiler, den ich verwende: VS 2010. Die Konvertierung von std :: function funktioniert zufällig.
Bartosz Milewski
3
Ja, der endgültige Wortlaut dieser Regel kam für VC2010 zu spät.
Ben Voigt
Ich habe ein Codebeispiel hinzugefügt. Hier ist ein vollständiges Programm .
JFS
@JFSebastian - Wie lang ist das Lambda in diesem Beispiel? Ist es lang genug, um das Ergebnis der Konvertierung in einen Funktionszeiger zu überleben?
Flexo
1
@JFSebastian - Ich kann anscheinend keine Antwort darauf finden, daher habe ich sie als eigenständige Frage gestellt: stackoverflow.com/questions/8026170/…
Flexo
21

Sie können die Lambda-Funktion von einer anderen Lambda-Funktion zurückgeben, da Sie den Rückgabetyp der Lambda-Funktion nicht explizit angeben sollten. Schreiben Sie einfach so etwas im globalen Bereich:

 auto retFun = []() {
     return [](int x) {return x;};
 };
dzhioev
quelle
2
Dies gilt nur, wenn das äußere Lambda nur aus der return-Anweisung besteht. Andernfalls müssen Sie den Rückgabetyp angeben.
Bartosz Milewski
3
Dies ist die beste Antwort, da kein Laufzeitpolymorphismus der std :: -Funktion erforderlich ist und Lambda eine nicht leere Erfassungsliste haben kann. Ich würde jedoch const auto fun = ...
robson3.14
2
@ BartoszMilewski nicht wahr seit C ++ 14.
Dschioev
19

Obwohl die Frage speziell nach C ++ 11 fragt, erlaubt C ++ 14 für andere, die darauf stoßen und Zugriff auf einen C ++ 14-Compiler haben, jetzt abgeleitete Rückgabetypen für normale Funktionen. Das Beispiel in der Frage kann also so angepasst werden, dass es wie gewünscht funktioniert, indem einfach die -> decltypeKlausel ... nach der Liste der Funktionsparameter gelöscht wird:

auto retFun()
{
    return [](int x) { return x; }
}

Beachten Sie jedoch, dass dies nicht funktioniert, wenn mehr als eine return <lambda>;in der Funktion angezeigt wird. Dies liegt daran, dass eine Einschränkung beim Abzug von Rückgabetypen darin besteht, dass alle Rückgabeanweisungen Ausdrücke desselben Typs zurückgeben müssen, aber jedes Lambda-Objekt vom Compiler einen eigenen eindeutigen Typ erhält, sodass die return <lambda>;Ausdrücke jeweils einen anderen Typ haben.

Anthony Hall
quelle
4
Warum die abgeleiteten Typen von c ++ 14 erwähnen, aber polymorphe Lambdas weglassen? auto retFun() { return [](auto const& x) { return x; }; }
sehe
1

Sie sollten so schreiben:

auto returnFunction = [](int x){
    return [&x](){
        return x;
    }();
};

um Ihre Rückgabe als Funktion zu erhalten und sie wie folgt zu verwenden:

int val = returnFunction(someNumber);
Terens Tare
quelle
0

Wenn Sie nicht über C ++ 11 verfügen und Ihren C ++ - Code beispielsweise auf Mikrocontrollern ausführen. Sie können einen ungültigen Zeiger zurückgeben und dann eine Umwandlung durchführen.

void* functionThatReturnsLambda()
{
    void(*someMethod)();

    // your lambda
    someMethod = []() {

        // code of lambda

    };

    return someMethod;
}


int main(int argc, char* argv[])
{

    void* myLambdaRaw = functionThatReturnsLambda();

    // cast it
    auto myLambda = (void(*)())myLambdaRaw;

    // execute lambda
    myLambda();
}
Tono Nam
quelle