Was ist ein Lambda-Ausdruck in C ++ 11?

1487

Was ist ein Lambda-Ausdruck in C ++ 11? Wann würde ich einen verwenden? Welche Problemklasse lösen sie, die vor ihrer Einführung nicht möglich war?

Ein paar Beispiele und Anwendungsfälle wären nützlich.

Nawaz
quelle
14
Ich habe einen Fall gesehen, in dem das Lambda sehr nützlich war: Ein Kollege von mir hat Code mit Millionen von Iterationen erstellt, um ein Problem mit der Raumoptimierung zu lösen. Der Algorithmus war bei Verwendung eines Lambdas viel schneller als eine ordnungsgemäße Funktion! Der Compiler ist Visual C ++ 2013.
Sergiol

Antworten:

1490

Das Problem

C ++ enthält nützliche generische Funktionen wie std::for_eachund std::transform, die sehr praktisch sein können. Leider kann die Verwendung auch sehr umständlich sein, insbesondere wenn der Funktor, den Sie anwenden möchten, für die jeweilige Funktion eindeutig ist.

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

Wenn Sie nur feinmal und an diesem bestimmten Ort verwenden, scheint es übertrieben, eine ganze Klasse zu schreiben, nur um etwas Triviales und Einmaliges zu tun.

In C ++ 03 könnten Sie versucht sein, Folgendes zu schreiben, um den Funktor lokal zu halten:

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

Dies ist jedoch nicht zulässig und fkann in C ++ 03 nicht an eine Vorlagenfunktion übergeben werden.

Die neue Lösung

In C ++ 11 werden Lambdas eingeführt, mit denen Sie einen anonymen Inline-Funktor schreiben können, der den ersetzt struct f. Für kleine einfache Beispiele kann dies sauberer zu lesen sein (es hält alles an einem Ort) und möglicherweise einfacher zu warten, zum Beispiel in der einfachsten Form:

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Lambda-Funktionen sind nur syntaktischer Zucker für anonyme Funktoren.

Rückgabetypen

In einfachen Fällen wird für Sie der Rückgabetyp des Lambda abgeleitet, z.

void func4(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(),
                 [](double d) { return d < 0.00001 ? 0 : d; }
                 );
}

Wenn Sie jedoch anfangen, komplexere Lambdas zu schreiben, werden Sie schnell auf Fälle stoßen, in denen der Rückgabetyp vom Compiler nicht abgeleitet werden kann, z.

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

Um dies zu beheben, können Sie explizit einen Rückgabetyp für eine Lambda-Funktion angeben, indem Sie Folgendes verwenden -> T:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

Variablen "erfassen"

Bisher haben wir nichts anderes verwendet als das, was an das Lambda darin übergeben wurde, aber wir können auch andere Variablen innerhalb des Lambda verwenden. Wenn Sie auf andere Variablen zugreifen möchten, können Sie die Capture-Klausel (die []des Ausdrucks) verwenden, die in diesen Beispielen bisher nicht verwendet wurde, z.

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

Sie können sowohl von Referenz- und Wert erfassen, die Sie verwenden können angeben &und =jeweils:

  • [&epsilon] Erfassung durch Referenz
  • [&] erfasst alle im Lambda verwendeten Variablen als Referenz
  • [=] Erfasst alle im Lambda verwendeten Variablen nach Wert
  • [&, epsilon] Erfasst Variablen wie mit [&], aber epsilon nach Wert
  • [=, &epsilon] erfasst Variablen wie mit [=], jedoch epsilon als Referenz

Die Generierung operator()erfolgt conststandardmäßig mit der Folge, dass die Erfassung standardmäßig erfolgt, constwenn Sie darauf zugreifen. Dies hat zur Folge, dass jeder Aufruf mit derselben Eingabe dasselbe Ergebnis liefert. Sie können jedoch das Lambda markieren, ummutable zu fordern, dass das operator()erzeugte nicht vorhanden ist const.

Flexo
quelle
9
@ Yakk du wurdest gefangen. Lambdas ohne Capture haben eine implizite Konvertierung in Funktionstypzeiger. Die Konvertierungsfunktion ist constimmer ...
Johannes Schaub - Litb
2
@ JohannesSchaub-litb oh hinterhältig - und es passiert, wenn Sie aufrufen ()- es wird als Lambda mit null Argumenten übergeben, aber da () constes nicht mit dem Lambda übereinstimmt, sucht es nach einer Typkonvertierung, die dies ermöglicht, einschließlich impliziter Umwandlung -to-function-pointer und ruft das dann auf! Hinterhältig!
Yakk - Adam Nevraumont
2
Interessant - Ich dachte ursprünglich, dass Lambdas eher anonyme Funktionen als Funktoren sind, und war verwirrt darüber, wie Captures funktionieren.
user253751
49
Wenn Sie Lambdas als Variablen in Ihrem Programm verwenden möchten, können Sie Folgendes verwenden: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Normalerweise lassen wir den Compiler den Typ ableiten: auto f = [](int a, bool b) -> double { ... }; (und vergessen Sie nicht #include <functional>)
Evert Heylen
11
Ich nehme an, nicht jeder versteht, warum return d < 0.00001 ? 0 : d;garantiert doppelt zurückgegeben wird, wenn einer der Operanden eine ganzzahlige Konstante ist (dies liegt an einer impliziten Heraufstufungsregel des Operators ?:, Bei der der 2. und 3. Operand durch die übliche Arithmetik gegeneinander abgewogen werden Konvertierungen, egal welche ausgewählt wird). Ein Wechsel zu 0.0 : dwürde das Beispiel vielleicht leichter verständlich machen.
Lundin
830

Was ist eine Lambda-Funktion?

Das C ++ - Konzept einer Lambda-Funktion stammt aus dem Lambda-Kalkül und der funktionalen Programmierung. Ein Lambda ist eine unbenannte Funktion, die (in der eigentlichen Programmierung, nicht in der Theorie) für kurze Codeausschnitte nützlich ist, die nicht wiederverwendet werden können und deren Benennung nicht wert ist.

In C ++ ist eine Lambda-Funktion wie folgt definiert

[]() { } // barebone lambda

oder in all seiner Pracht

[]() mutable -> T { } // T is the return type, still lacking throw()

[]ist die Erfassungsliste, ()die Argumentliste und {}der Funktionskörper.

Die Erfassungsliste

Die Erfassungsliste definiert, was von außerhalb des Lambda innerhalb des Funktionskörpers verfügbar sein soll und wie. Es kann entweder sein:

  1. ein Wert: [x]
  2. eine Referenz [& x]
  3. jede Variable, die derzeit durch Bezugnahme im Geltungsbereich liegt [&]
  4. wie 3, jedoch nach Wert [=]

Sie können eine der oben genannten Optionen in einer durch Kommas getrennten Liste mischen [x, &y].

Die Argumentliste

Die Argumentliste ist dieselbe wie in jeder anderen C ++ - Funktion.

Der Funktionskörper

Der Code, der ausgeführt wird, wenn das Lambda tatsächlich aufgerufen wird.

Abzug vom Rückgabetyp

Wenn ein Lambda nur eine return-Anweisung hat, kann der return-Typ weggelassen werden und hat den impliziten Typ von decltype(return_statement).

Veränderlich

Wenn ein Lambda als veränderlich markiert ist (z. B. []() mutable { }), darf es die Werte mutieren, die durch den Wert erfasst wurden.

Anwendungsfälle

Die durch den ISO-Standard definierte Bibliothek profitiert stark von Lambdas und erhöht die Benutzerfreundlichkeit um mehrere Balken, da Benutzer ihren Code jetzt nicht mehr mit kleinen Funktoren in einem zugänglichen Bereich überladen müssen.

C ++ 14

In C ++ 14 wurden Lambdas um verschiedene Vorschläge erweitert.

Initialisierte Lambda-Aufnahmen

Ein Element der Erfassungsliste kann jetzt mit initialisiert werden =. Dies ermöglicht das Umbenennen von Variablen und das Erfassen durch Verschieben. Ein Beispiel aus dem Standard:

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

und eine aus Wikipedia, die zeigt, wie man mit std::move:

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

Generische Lambdas

Lambdas können jetzt generisch sein ( autowäre hier äquivalent, Twenn Tsich irgendwo im umgebenden Bereich ein Typvorlagenargument befindet ):

auto lambda = [](auto x, auto y) {return x + y;};

Verbesserter Abzug vom Rückgabetyp

C ++ 14 erlaubt abgeleitete Rückgabetypen für jede Funktion und beschränkt sie nicht auf Funktionen des Formulars return expression;. Dies gilt auch für Lambdas.

pmr
quelle
2
Warum beenden Sie in Ihrem obigen Beispiel für initialisierte Lambda-Captures die Lamba-Funktion mit ();? Dies erscheint wie [] () {} (); Anstatt von [](){};. Sollte der Wert von x auch nicht 5 sein?
Ramakrishnan Kannan
6
@RamakrishnanKannan: 1) Die () sind dazu da, das Lambda direkt nach der Definition aufzurufen und y seinen Rückgabewert zu geben. Die Variable y ist eine ganze Zahl, nicht das Lambda. 2) Nein, x = 5 ist lokal für das Lambda (eine Erfassung nach Wert, die zufällig denselben Namen wie die äußere Bereichsvariable x hat), und dann wird x + 2 = 5 + 2 zurückgegeben. Die Neuzuweisung der äußeren Variablen x erfolgt über die Referenz r : r = &x; r += 2;, dies geschieht jedoch mit dem ursprünglichen Wert von 4.
The Vee
167

Lambda-Ausdrücke werden normalerweise verwendet, um Algorithmen zu kapseln, damit sie an eine andere Funktion übergeben werden können. Allerdings ist es möglich , eine Lambda unmittelbar nach Definition auszuführen :

[&](){ ...your code... }(); // immediately executed lambda expression

ist funktional äquivalent zu

{ ...your code... } // simple code block

Dies macht Lambda-Ausdrücke zu einem leistungsstarken Werkzeug für die Umgestaltung komplexer Funktionen . Sie beginnen, indem Sie einen Codeabschnitt wie oben gezeigt in eine Lambda-Funktion einschließen. Der Prozess der expliziten Parametrisierung kann dann nach jedem Schritt schrittweise mit Zwischentests durchgeführt werden. Sobald Sie den Codeblock vollständig parametrisiert haben (wie durch das Entfernen des Codeblocks gezeigt &), können Sie den Code an einen externen Speicherort verschieben und zu einer normalen Funktion machen.

Ebenso können Sie Lambda-Ausdrücke verwenden, um Variablen basierend auf dem Ergebnis eines Algorithmus zu initialisieren ...

int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!

Als eine Möglichkeit , Ihre Programmlogik der Partitionierung , könnten Sie sogar finden es sinnvoll , einen Lambda - Ausdruck als Argument für einen anderen Lambda - Ausdruck passieren ...

[&]( std::function<void()> algorithm ) // wrapper section
   {
   ...your wrapper code...
   algorithm();
   ...your wrapper code...
   }
([&]() // algorithm section
   {
   ...your algorithm code...
   });

Mit Lambda-Ausdrücken können Sie auch benannte verschachtelte Funktionen erstellen. Dies kann eine bequeme Möglichkeit sein, doppelte Logik zu vermeiden. Die Verwendung von benannten Lambdas ist auch für die Augen etwas einfacher (im Vergleich zu anonymen Inline-Lambdas), wenn eine nicht triviale Funktion als Parameter an eine andere Funktion übergeben wird. Hinweis: Vergessen Sie das Semikolon nach der schließenden geschweiften Klammer nicht.

auto algorithm = [&]( double x, double m, double b ) -> double
   {
   return m*x+b;
   };

int a=algorithm(1,2,3), b=algorithm(4,5,6);

Wenn die nachfolgende Profilerstellung einen erheblichen Initialisierungsaufwand für das Funktionsobjekt ergibt, können Sie dies als normale Funktion umschreiben.

kein Balken
quelle
11
Haben Sie bemerkt, dass diese Frage vor 1,5 Jahren gestellt wurde und dass die letzte Aktivität vor fast 1 Jahr war? Wie auch immer, Sie bringen einige interessante Ideen ein, die ich noch nie gesehen habe!
Piotr99
7
Vielen Dank für den Tipp zum gleichzeitigen Definieren und Ausführen! Ich denke, es ist erwähnenswert, dass dies als Voraussetzung für ifAussagen funktioniert : if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespaceVorausgesetzt, es ihandelt sich um einstd::string
Blacklight Shining
74
Das Folgende ist also ein rechtlicher Ausdruck : [](){}();.
Nobar
8
Pfui! Die (lambda: None)()Syntax von Python ist viel besser lesbar.
Dan04
9
@nobar - du hast recht, ich habe falsch geschrieben. Dies ist legal (ich habe es dieses Mal getestet)main() {{{{((([](){{}}())));}}}}
Mark Lakata
38

Antworten

F: Was ist ein Lambda-Ausdruck in C ++ 11?

A: Unter der Haube ist es das Objekt einer automatisch generierten Klasse mit Überladungsoperator () const . Ein solches Objekt wird als Closure bezeichnet und vom Compiler erstellt. Dieses 'Closure'-Konzept kommt dem Bind-Konzept aus C ++ 11 nahe. Aber Lambdas generieren normalerweise besseren Code. Und Anrufe durch Verschlüsse ermöglichen ein vollständiges Inlining.

F: Wann würde ich einen verwenden?

A: Um "einfache und kleine Logik" zu definieren und den Compiler zu bitten, führen Sie die Generierung aus der vorherigen Frage durch. Sie geben einem Compiler einige Ausdrücke, die Sie in operator () haben möchten. Alle anderen Sachen, die der Compiler für Sie generiert.

F: Welche Klasse von Problemen lösen sie, die vor ihrer Einführung nicht möglich waren?

A: Es ist eine Art Syntaxzucker wie das Überladen von Operatoren anstelle von Funktionen für benutzerdefinierte Add-, Subrtact- Operationen ... Es werden jedoch mehr Zeilen nicht benötigten Codes gespeichert , um 1-3 Zeilen echte Logik in einige Klassen usw. zu verpacken! Einige Ingenieure sind der Meinung, dass bei einer geringeren Anzahl von Zeilen die Wahrscheinlichkeit geringer ist, dass Fehler auftreten (ich denke auch).

Anwendungsbeispiel

auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);

Extras über Lambdas, nicht in Frage gestellt. Ignorieren Sie diesen Abschnitt, wenn Sie nicht interessiert sind

1. Erfasste Werte. Was können Sie erfassen

1.1. Sie können auf eine Variable mit statischer Speicherdauer in Lambdas verweisen. Sie alle werden gefangen genommen.

1.2. Sie können Lambda verwenden, um Werte "nach Wert" zu erfassen. In diesem Fall werden erfasste Variablen in das Funktionsobjekt kopiert (Abschluss).

[captureVar1,captureVar2](int arg1){}

1.3. Sie können als Referenz erfassen. & - bedeutet in diesem Zusammenhang Referenz, keine Zeiger.

   [&captureVar1,&captureVar2](int arg1){}

1.4. Es gibt eine Notation, um alle nicht statischen Variablen nach Wert oder Referenz zu erfassen

  [=](int arg1){} // capture all not-static vars by value

  [&](int arg1){} // capture all not-static vars by reference

1.5. Es gibt eine Notation, um alle nicht statischen Variablen nach Wert oder Referenz zu erfassen und smth anzugeben. Mehr. Beispiele: Erfassen Sie alle nicht statischen Variablen nach Wert, aber nach Referenzerfassung Param2

[=,&Param2](int arg1){} 

Erfassen Sie alle nicht statischen Variablen anhand der Referenz, aber anhand der Werterfassung Param2

[&,Param2](int arg1){} 

2. Geben Sie den Typabzug zurück

2.1. Der Lambda-Rückgabetyp kann abgeleitet werden, wenn Lambda ein Ausdruck ist. Oder Sie können es explizit angeben.

[=](int arg1)->trailing_return_type{return trailing_return_type();}

Wenn Lambda mehr als einen Ausdruck hat, muss der Rückgabetyp über den nachfolgenden Rückgabetyp angegeben werden. Eine ähnliche Syntax kann auch auf automatische Funktionen und Elementfunktionen angewendet werden

3. Erfasste Werte. Was Sie nicht erfassen können

3.1. Sie können nur lokale Variablen erfassen, keine Mitgliedsvariable des Objekts.

4. versUmwandlungen

4.1 !! Lambda ist kein Funktionszeiger und keine anonyme Funktion, aber lambdas ohne Erfassung können implizit in einen Funktionszeiger konvertiert werden.

ps

  1. Weitere Informationen zur Lambda-Grammatik finden Sie im Arbeitsentwurf für die Programmiersprache C ++ # 337, 16.01.2012, 5.1.2. Lambda Expressions, S.88

  2. In C ++ 14 wurde die zusätzliche Funktion "Init Capture" hinzugefügt. Es ermöglicht die willkürliche Deklaration von Mitgliedern der Schließungsdaten:

    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
Bruziuz
quelle
Dies [&,=Param2](int arg1){}scheint keine gültige Syntax zu sein. Die richtige Form wäre[&,Param2](int arg1){}
GetFree
Vielen Dank. Zuerst habe ich versucht, dieses Snippet zu kompilieren. Und es scheint seltsam, Assymetrie in zulässigen Modifikatoren in der Erfassungsliste // g ++ -std = c ++ 11 main.cpp -o test_bin; ./test_bin #include <stdio.h> int main () {#if 1 {int param = 0; auto f = [=, & param] (int arg1) veränderlich {param = arg1;}; f (111); printf ("% i \ n", param); } #endif #if 0 {int param = 0; auto f = [&, = param] (int arg1) veränderlich {param = arg1;}; f (111); printf ("% i \ n", param); } #endif return 0; }
Bruziuz
Sieht so aus, als würde diese neue Zeile im Kommentar nicht unterstützt. Dann öffnete ich 5.1.2 Lambda-Ausdrücke, S.88, "Working Draft, Standard für die Programmiersprache C ++", Dokumentnummer: # 337, 2012-01-16. Und schaute in die Grammatiksyntax. Und du hast recht. Es gibt keine Gefangennahme über "= arg"
bruziuz
Vielen Dank, habe es in der Beschreibung behoben und auch neues Wissen dazu erworben.
Bruziuz
16

Eine Lambda-Funktion ist eine anonyme Funktion, die Sie inline erstellen. Es kann Variablen erfassen, wie einige erklärt haben (z. B. http://www.stroustrup.com/C++11FAQ.html#lambda ), es gibt jedoch einige Einschränkungen. Wenn es beispielsweise eine solche Rückrufschnittstelle gibt,

void apply(void (*f)(int)) {
    f(10);
    f(20);
    f(30);
}

Sie können eine Funktion an Ort und Stelle schreiben, um sie wie die unten verwendete zu verwenden:

int col=0;
void output() {
    apply([](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

Aber das kannst du nicht machen:

void output(int n) {
    int col=0;
    apply([&col,n](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

aufgrund von Einschränkungen im C ++ 11-Standard. Wenn Sie Captures verwenden möchten, müssen Sie sich auf die Bibliothek und verlassen

#include <functional> 

(oder einen anderen STL-Bibliotheks-ähnlichen Algorithmus, um ihn indirekt abzurufen) und dann mit std :: function zu arbeiten, anstatt normale Funktionen als Parameter wie diesen zu übergeben:

#include <functional>
void apply(std::function<void(int)> f) {
    f(10);
    f(20);
    f(30);
}
void output(int width) {
    int col;
    apply([width,&col](int data) {
        cout << data << ((++col % width) ? ' ' : '\n');
    });
}
Ted
quelle
1
Der Grund ist, dass ein Lambda nur dann in einen Funktionszeiger konvertieren kann, wenn es keine Erfassung hat. Wenn applyes eine Vorlage wäre, die einen Funktor akzeptiert, würde sie funktionieren
sp2danny
1
Das Problem ist jedoch, dass Sie, wenn apply eine vorhandene Schnittstelle ist, möglicherweise nicht den Luxus haben, sie anders als eine einfache alte Funktion deklarieren zu können. Der Standard könnte so konzipiert sein, dass bei jeder Ausführung eines solchen Lambda-Ausdrucks eine neue Instanz einer einfachen alten Funktion mit generierten fest codierten Verweisen auf die erfassten Variablen generiert werden kann. Es scheint, dass zur Kompilierungszeit eine Lambda-Funktion generiert wird. Es gibt auch andere Konsequenzen. Beispiel: Wenn Sie eine statische Variable deklarieren, erhalten Sie keine neue statische Variable, auch wenn Sie den Lambda-Ausdruck neu auswerten.
Ted
1
Funktionszeiger sollen häufig gespeichert werden, und eine Lambdas-Erfassung kann außerhalb des Gültigkeitsbereichs liegen. dass nur
lambdas ohne
1
Sie müssen immer noch darauf achten, dass Stapelvariablen aus demselben Grund freigegeben werden. Siehe blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… Das Beispiel, das ich mit output and apply geschrieben habe, ist so geschrieben, dass Funktionszeiger auch funktionieren würden, wenn sie stattdessen zugelassen und verwendet würden. Die Spalte bleibt zugewiesen, bis alle Funktionsaufrufe von apply beendet sind. Wie würden Sie diesen Code so umschreiben, dass er mit der vorhandenen Apply-Schnittstelle funktioniert? Würden Sie am Ende globale oder statische Variablen oder eine dunkelere Transformation des Codes verwenden?
Ted
1
oder vielleicht meinen Sie einfach, dass Lambda-Ausdrücke r-Werte und daher temporär sind, der Code jedoch konstant bleibt (Singleton / Static), damit er in Zukunft aufgerufen werden kann. In diesem Fall sollte die Funktion möglicherweise zugewiesen bleiben, solange die vom Stapel zugewiesenen Erfassungen zugewiesen bleiben. Natürlich kann es unordentlich werden, wenn beispielsweise viele Variationen der Funktion in einer Schleife zugewiesen werden.
Ted
12

Eine der besten Erklärungen dafür lambda expressiongibt der Autor von C ++ Bjarne Stroustrup in seinem Buch ***The C++ Programming Language***Kapitel 11 ( ISBN-13: 978-0321563842 ):

What is a lambda expression?

Ein Lambda-Ausdruck , der manchmal auch als Lambda- Funktion oder (streng genommen falsch, aber umgangssprachlich) als Lambda bezeichnet wird , ist eine vereinfachte Notation zum Definieren und Verwenden eines anonymen Funktionsobjekts . Anstatt eine benannte Klasse mit einem Operator () zu definieren, später ein Objekt dieser Klasse zu erstellen und es schließlich aufzurufen, können wir eine Kurzform verwenden.

When would I use one?

Dies ist besonders nützlich, wenn wir eine Operation als Argument an einen Algorithmus übergeben möchten. Im Zusammenhang mit grafischen Benutzeroberflächen (und anderswo) werden solche Vorgänge häufig als Rückrufe bezeichnet .

What class of problem do they solve that wasn't possible prior to their introduction?

Hier denke ich, dass jede Aktion, die mit Lambda-Ausdruck ausgeführt wird, ohne sie gelöst werden kann, aber mit viel mehr Code und viel größerer Komplexität. Lambda-Ausdruck Dies ist die Art der Optimierung Ihres Codes und eine Möglichkeit, ihn attraktiver zu machen. So traurig von Stroustup:

effektive Möglichkeiten zur Optimierung

Some examples

über Lambda-Ausdruck

void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
    for_each(begin(v),end(v),
        [&os,m](int x) { 
           if (x%m==0) os << x << '\n';
         });
}

oder über Funktion

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

oder auch

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(int x) const
           { 
               if (x%m==0) os << x << '\n';
           }
     };
     for_each(begin(v),end(v),Modulo_print{os,m}); 
}

Wenn du brauchst, kannst du lambda expressionwie folgt benennen :

void print_modulo(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
      for_each(begin(v),end(v),Modulo_print);
 }

Oder nehmen Sie eine andere einfache Probe an

void TestFunctions::simpleLambda() {
    bool sensitive = true;
    std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});

    sort(v.begin(),v.end(),
         [sensitive](int x, int y) {
             printf("\n%i\n",  x < y);
             return sensitive ? x < y : abs(x) < abs(y);
         });


    printf("sorted");
    for_each(v.begin(), v.end(),
             [](int x) {
                 printf("x - %i;", x);
             }
             );
}

wird als nächstes generieren

0

1

0

1

0

1

0

1

0

1

0 sortiert x - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;

[]- Dies ist eine Erfassungsliste oder lambda introducer: Wenn lambdaskein Zugriff auf die lokale Umgebung erforderlich ist, können wir sie verwenden.

Zitat aus dem Buch:

Das erste Zeichen eines Lambda-Ausdrucks ist immer [ . Ein Lambda-Einführer kann verschiedene Formen annehmen:

[] : Eine leere Erfassungsliste. Dies impliziert, dass im Lambda-Körper keine lokalen Namen aus dem umgebenden Kontext verwendet werden können. Für solche Lambda-Ausdrücke werden Daten aus Argumenten oder aus nichtlokalen Variablen erhalten.

[&] : implizit als Referenz erfassen. Alle lokalen Namen können verwendet werden. Auf alle lokalen Variablen wird als Referenz zugegriffen.

[=] : implizit nach Wert erfassen. Alle lokalen Namen können verwendet werden. Alle Namen beziehen sich auf Kopien der lokalen Variablen, die am Aufrufpunkt des Lambda-Ausdrucks erstellt wurden.

[Erfassungsliste]: explizite Erfassung; Die Erfassungsliste ist die Liste der Namen lokaler Variablen, die erfasst werden sollen (dh im Objekt gespeichert sind), nach Referenz oder Wert. Variablen mit Namen vor & werden als Referenz erfasst. Andere Variablen werden nach Wert erfasst. Eine Erfassungsliste kann auch diese und Namen enthalten, gefolgt von ... als Elemente.

[&, Erfassungsliste] : Erfassen Sie implizit alle lokalen Variablen mit Namen, die nicht in der Liste aufgeführt sind. Die Erfassungsliste kann dies enthalten. Aufgelistete Namen dürfen nicht vor & stehen. In der Erfassungsliste genannte Variablen werden nach Wert erfasst.

[=, Erfassungsliste] : Erfasst implizit alle lokalen Variablen mit Namen, die nicht in der Liste aufgeführt sind, nach Wert. Die Erfassungsliste kann dies nicht enthalten. Vor den aufgeführten Namen muss & stehen. In der Erfassungsliste genannte Variablen werden als Referenz erfasst.

Beachten Sie, dass ein lokaler Name, dem & vorangestellt ist, immer als Referenz erfasst wird und ein lokaler Name, dem & nicht vorangestellt ist, immer als Wert erfasst wird. Nur die Erfassung durch Referenz ermöglicht das Ändern von Variablen in der aufrufenden Umgebung.

Additional

Lambda expression Format

Geben Sie hier die Bildbeschreibung ein

Zusätzliche Referenzen:

gbk
quelle
Schöne Erklärung. Mit bereichsbasierten for-Schleifen können Sie Lambdas vermeiden und den Code verkürzenfor (int x : v) { if (x % m == 0) os << x << '\n';}
Dietrich Baumgarten
2

Nun, eine praktische Anwendung, die ich herausgefunden habe, ist das Reduzieren des Kesselplattencodes. Zum Beispiel:

void process_z_vec(vector<int>& vec)
{
  auto print_2d = [](const vector<int>& board, int bsize)
  {
    for(int i = 0; i<bsize; i++)
    {
      for(int j=0; j<bsize; j++)
      {
        cout << board[bsize*i+j] << " ";
      }
      cout << "\n";
    }
  };
  // Do sth with the vec.
  print_2d(vec,x_size);
  // Do sth else with the vec.
  print_2d(vec,y_size);
  //... 
}

Ohne Lambda müssen Sie möglicherweise etwas für verschiedene bsizeFälle tun . Natürlich könnten Sie eine Funktion erstellen, aber was ist, wenn Sie die Verwendung im Rahmen der Soul-User-Funktion einschränken möchten? Die Natur von Lambda erfüllt diese Anforderung und ich benutze sie für diesen Fall.

Fehlentwicklung
quelle
2

Die Lambdas in c ++ werden als "on the go available function" behandelt. Ja, es ist buchstäblich unterwegs. Sie definieren es. benutze es; und wenn der übergeordnete Funktionsumfang beendet ist, ist die Lambda-Funktion weg.

c ++ führte es in c ++ 11 ein und jeder begann es wie an jedem möglichen Ort zu benutzen. Das Beispiel und was Lambda ist, finden Sie hier https://en.cppreference.com/w/cpp/language/lambda

Ich werde beschreiben, was nicht da ist, aber für jeden C ++ - Programmierer unbedingt zu wissen

Lambda soll nicht überall verwendet werden und jede Funktion kann nicht durch Lambda ersetzt werden. Es ist auch nicht das schnellste im Vergleich zur normalen Funktion. weil es einige Gemeinkosten hat, die von Lambda gehandhabt werden müssen.

In einigen Fällen wird dies sicherlich dazu beitragen, die Anzahl der Zeilen zu verringern. Es kann grundsätzlich für den Codeabschnitt verwendet werden, der einmal oder mehrmals in derselben Funktion aufgerufen wird, und dieser Code wird an keiner anderen Stelle benötigt, sodass Sie eine eigenständige Funktion dafür erstellen können.

Unten sehen Sie das grundlegende Beispiel für Lambda und was im Hintergrund passiert.

Benutzercode:

int main()
{
  // Lambda & auto
  int member=10;
  auto endGame = [=](int a, int b){ return a+b+member;};

  endGame(4,5);

  return 0;

}

Wie compile es erweitert:

int main()
{
  int member = 10;

  class __lambda_6_18
  {
    int member;
    public: 
    inline /*constexpr */ int operator()(int a, int b) const
    {
      return a + b + member;
    }

    public: __lambda_6_18(int _member)
    : member{_member}
    {}

  };

  __lambda_6_18 endGame = __lambda_6_18{member};
  endGame.operator()(4, 5);

  return 0;
}

Wie Sie sehen können, wird der Overhead hinzugefügt, wenn Sie ihn verwenden. Es ist also keine gute Idee, sie überall zu verwenden. Es kann an Orten verwendet werden, an denen sie anwendbar sind.

Sachin Nale
quelle
Ja, es ist buchstäblich unterwegs. Sie definieren es. benutze es; und wenn der übergeordnete Funktionsumfang beendet ist, ist die Lambda-Funktion weg . Was ist, wenn die Funktion das Lambda an den Aufrufer zurückgibt?
Nawaz
1
Es ist auch nicht das schnellste im Vergleich zur normalen Funktion. weil es einige Gemeinkosten hat, die von Lambda gehandhabt werden müssen. Haben Sie jemals wirklich jede Benchmark laufen diese Behauptung zu unterstützen ? Im Gegenteil, Lambda + -Vorlagen erzeugen häufig den schnellstmöglichen Code.
Nawaz
1

Ein Problem, das es löst: Code einfacher als Lambda für einen Aufruf im Konstruktor, der eine Ausgabeparameterfunktion zum Initialisieren eines const-Elements verwendet

Sie können ein const-Mitglied Ihrer Klasse mit einem Aufruf einer Funktion initialisieren, die ihren Wert festlegt, indem Sie ihre Ausgabe als Ausgabeparameter zurückgeben.

Sergiol
quelle
Dies kann auch mit einer einfachen Funktion erfolgen. Dies ist sogar das, was die akzeptierte Antwort auf die Frage, mit der Sie verlinkt haben, vorschreibt.
SirGuy