Suche…


Syntax

  • [ default-capture , capture-list ] ( Argumentliste ) veränderliche Attribute für Throw-Spezifikation -> Rückgabetyp { lambda-body } // Reihenfolge der Lambda-Bezeichner und Attribute
  • [ Erfassungsliste ] ( Argumentliste ) { Lambda-Body } // Allgemeine Lambda-Definition.
  • [=] ( argument-list ) { lambda-body } // Erfasst alle benötigten lokalen Variablen nach Wert.
  • [&] ( argument-list ) { lambda-body } // Erfasst alle benötigten lokalen Variablen per Verweis.
  • [ capture-list ] { lambda-body } // Argumentliste und Angaben können weggelassen werden.

Parameter

Parameter Einzelheiten
Standard-Capture Gibt an, wie alle nicht aufgelisteten Variablen erfasst werden. Kann = (Erfassung nach Wert) oder & (Aufnahme nach Referenz) sein. Ohne Angabe sind nicht aufgeführte Variablen im Lambda-Body nicht erreichbar . Das Standard-Capture muss vor der Capture-Liste stehen .
Capture-Liste Gibt an, wie lokale Variablen im Lambda-Body verfügbar gemacht werden . Variablen ohne Präfix werden nach Wert erfasst. Mit & Variablen werden durch Verweis erfasst. Innerhalb einer Klassenmethode, this kann verwendet werden , um alle zugänglich ihre Mitglieder durch Bezugnahme zu machen. Auf nicht aufgelistete Variablen kann nicht zugegriffen werden, es sei denn, der Liste ist eine Standarderfassung vorangestellt.
Argumentliste Gibt die Argumente der Lambda-Funktion an.
veränderlich (optional) Normalerweise werden durch Wert erfasste Variablen const . Durch die Angabe der mutable Eigenschaft werden sie nicht konstant. Änderungen an diesen Variablen werden zwischen Aufrufen beibehalten.
Wurfspezifikation (optional) Gibt das Auslöseverhalten der Lambda-Funktion an. Zum Beispiel: noexcept oder throw(std::exception) .
Attribute (optional) Beliebige Attribute für die Lambda-Funktion. Wenn zum Beispiel der Lambda-Body immer eine Ausnahme [[noreturn]] kann [[noreturn]] verwendet werden.
-> Rückgabetyp (optional) Gibt den Rückgabetyp der Lambda-Funktion an. Erforderlich, wenn der Rückgabetyp nicht vom Compiler bestimmt werden kann.
Lambda-Körper Ein Codeblock, der die Implementierung der Lambda-Funktion enthält.

Bemerkungen

C ++ 17 (der aktuelle Entwurf) führt constexpr Lambdas ein, im Wesentlichen Lambdas, die zur Kompilierzeit ausgewertet werden können. Ein Lambda ist automatisch constexpr wenn es die Anforderungen von constexpr erfüllt. Sie können es jedoch auch mit dem Schlüsselwort constexpr angeben:

//Explicitly define this lambdas as constexpr
[]() constexpr {
    //Do stuff
}

Was ist ein Lambda-Ausdruck?

Mit einem Lambda-Ausdruck können Sie einfache Funktionsobjekte auf einfache Weise erstellen. Ein Lambda-Ausdruck ist ein Prvalue, dessen Ergebnisobjekt Schließobjekt genannt wird , das sich wie ein Funktionsobjekt verhält.

Der Name "Lambda-Ausdruck" stammt von Lambda-Kalkül , einem mathematischen Formalismus, der in den 1930er Jahren von Alonzo Church zur Untersuchung von Fragen zu Logik und Berechenbarkeit erfunden wurde. Lambda-Kalkül bildete die Grundlage von LISP , einer funktionalen Programmiersprache. Im Vergleich zu Lambda-Kalkül und LISP weisen C ++ - Lambda-Ausdrücke die Eigenschaften auf, unbenannt zu sein und Variablen aus dem umgebenden Kontext zu erfassen, ihnen fehlt jedoch die Fähigkeit, Funktionen auszuführen und zurückzugeben.

Ein Lambda-Ausdruck wird häufig als Argument für Funktionen verwendet, die ein aufrufbares Objekt enthalten. Dies kann einfacher sein als das Erstellen einer benannten Funktion, die nur verwendet wird, wenn sie als Argument übergeben wird. In solchen Fällen werden im Allgemeinen Lambda-Ausdrücke bevorzugt, da sie es ermöglichen, die Funktionsobjekte inline zu definieren.

Ein Lambda besteht normalerweise aus drei Teilen: einer Erfassungsliste [] , einer optionalen Parameterliste () und einem Hauptteil {} , die alle leer sein können:

[](){}                // An empty lambda, which does and returns nothing

Aufnahmeliste

[] ist die Erfassungsliste . Auf Variablen des umschließenden Bereichs kann standardmäßig kein Lambda zugreifen. Durch das Erfassen einer Variablen ist sie innerhalb des Lambda als Kopie oder als Referenz verfügbar . Die erfassten Variablen werden Teil des Lambda. Im Gegensatz zu Funktionsargumenten müssen sie beim Aufruf des Lambda nicht übergeben werden.

int a = 0;                       // Define an integer variable
auto f = []()   { return a*9; }; // Error: 'a' cannot be accessed
auto f = [a]()  { return a*9; }; // OK, 'a' is "captured" by value
auto f = [&a]() { return a++; }; // OK, 'a' is "captured" by reference
                                 //      Note: It is the responsibility of the programmer
                                 //      to ensure that a is not destroyed before the
                                 //      lambda is called.
auto b = f();                    // Call the lambda function. a is taken from the capture list and not passed here.

Parameterliste

() ist die Parameterliste , die fast die gleiche ist wie bei regulären Funktionen. Wenn das Lambda keine Argumente akzeptiert, können diese Klammern weggelassen werden (es sei denn, Sie müssen das Lambda als mutable deklarieren). Diese beiden Lambdas sind gleichwertig:

auto call_foo  = [x](){ x.foo(); };
auto call_foo2 = [x]{ x.foo(); };
C ++ 14

Die Parameterliste kann anstelle von tatsächlichen Typen den Platzhaltertyp auto verwenden. Dadurch verhält sich dieses Argument wie ein Vorlagenparameter einer Funktionsvorlage. Die folgenden Lambdas sind gleichwertig, wenn Sie einen Vektor in generischem Code sortieren möchten:

auto sort_cpp11 = [](std::vector<T>::const_reference lhs, std::vector<T>::const_reference rhs) { return lhs < rhs; }; 
auto sort_cpp14 = [](const auto &lhs, const auto &rhs) { return lhs < rhs; }; 

Funktionskörper

{} ist der Körper , der dem von regulären Funktionen entspricht.


Ein Lambda anrufen

Das Ergebnisobjekt eines Lambda-Ausdrucks ist eine Schließung , die mit dem operator() aufgerufen werden kann (wie bei anderen Funktionsobjekten):

int multiplier = 5;
auto timesFive = [multiplier](int a) { return a * multiplier; }; 
std::out << timesFive(2); // Prints 10

multiplier = 15;
std::out << timesFive(2); // Still prints 2*5 == 10

Rückgabetyp

Standardmäßig wird der Rückgabetyp eines Lambda-Ausdrucks abgeleitet.

[](){ return true; };

In diesem Fall ist der Rückgabetyp bool .

Sie können den Rückgabetyp auch manuell mit der folgenden Syntax angeben:

[]() -> bool { return true; };

Mutable Lambda

Objekte, die im Lambda-Wert erfasst werden, sind standardmäßig unveränderlich. Dies liegt daran, dass der operator() des generierten Abschlussobjekts standardmäßig const ist.

auto func = [c = 0](){++c; std::cout << c;};  // fails to compile because ++c
                                              // tries to mutate the state of
                                              // the lambda.

Das Ändern kann mit dem Schlüsselwort mutable erlaubt werden, wodurch der operator() des näheren Objekts nicht- const :

auto func = [c = 0]() mutable {++c; std::cout << c;};

Bei Verwendung zusammen mit dem Rückgabetyp wird das mutable davor mutable .

auto func = [c = 0]() mutable -> int {++c; std::cout << c; return c;};

Ein Beispiel zur Veranschaulichung der Nützlichkeit von Lambdas

Vor C ++ 11:

C ++ 11
// Generic functor used for comparison
struct islessthan
{
    islessthan(int threshold) : _threshold(threshold) {}

    bool operator()(int value) const
    {
        return value < _threshold;
    }
private:
    int _threshold;
};

// Declare a vector
const int arr[] = { 1, 2, 3, 4, 5 };
std::vector<int> vec(arr, arr+5);

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(), islessthan(threshold));

Seit C ++ 11:

C ++ 11
// Declare a vector
std::vector<int> vec{ 1, 2, 3, 4, 5 };

// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
auto it = std::find_if(vec.begin(), vec.end(), [threshold](int value) { return value < threshold; });

Angabe des Rückgabetyps

Bei Lambdas mit einer einzelnen return-Anweisung oder mehreren return-Anweisungen, deren Ausdrücke denselben Typ haben, kann der Compiler den Rückgabetyp ableiten:

// Returns bool, because "value > 10" is a comparison which yields a Boolean result
auto l = [](int value) {
    return value > 10;
}

Bei Lambdas mit mehreren return-Anweisungen verschiedener Typen kann der Compiler den Rückgabetyp nicht ableiten:

// error: return types must match if lambda has unspecified return type
auto l = [](int value) {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

In diesem Fall müssen Sie den Rückgabetyp explizit angeben:

// The return type is specified explicitly as 'double'
auto l = [](int value) -> double {
    if (value < 10) {
        return 1;
    } else {
        return 1.5;
    }
};

Die Regeln hierfür entsprechen den Regeln für die auto Typabzug. Lambdas ohne explizit angegebene Rückgabetypen geben niemals Referenzen zurück. Wenn ein Referenztyp gewünscht wird, muss er auch explizit angegeben werden:

auto copy = [](X& x) { return x; };       // 'copy' returns an X, so copies its input
auto ref  = [](X& x) -> X& { return x; }; // 'ref' returns an X&, no copy

Erfassung nach Wert

Wenn Sie den Namen der Variablen in der Erfassungsliste angeben, erfasst der Lambda ihn nach Wert. Dies bedeutet, dass der generierte Abschlusstyp für das Lambda eine Kopie der Variablen speichert. Dies erfordert auch , dass die Art Variable sein copy-konstruierbar:

int a = 0;

[a]() {
    return a;   // Ok, 'a' is captured by value
};
C ++ 14
auto p = std::unique_ptr<T>(...);

[p]() {         // Compile error; `unique_ptr` is not copy-constructible
    return p->createWidget(); 
};

Ab C ++ 14 können Variablen vor Ort initialisiert werden. Auf diese Weise können nur Arten von Bewegungen im Lambda erfasst werden.

C ++ 14
auto p = std::make_unique<T>(...);

[p = std::move(p)]() {
    return p->createWidget(); 
};

Obwohl ein Lambda Variablen nach Wert erfasst, wenn sie durch ihren Namen angegeben werden, können diese Variablen nicht standardmäßig innerhalb des Lambda-Körpers geändert werden. Dies liegt daran, dass der Schließungstyp den Lambda-Body in eine Deklaration von operator() const .

Die const gilt für Zugriffe auf Membervariablen des Abschlusstyps und erfasste Variablen, die Mitglieder des Closures sind (alle gegenteiligen Anschein)

int a = 0;

[a]() {
    a = 2;      // Illegal, 'a' is accessed via `const`

    decltype(a) a1 = 1; 
    a1 = 2; // valid: variable 'a1' is not const
};

Um das const zu entfernen, müssen Sie das Schlüsselwort mutable im Lambda angeben:

int a = 0;

[a]() mutable {
    a = 2;      // OK, 'a' can be modified
    return a;
};

Da a durch Wert erfasst wurde, wirken sich Änderungen durch Aufruf des Lambda nicht auf a . Der Wert von a wurde in den Lambda kopiert , wenn es gebaut wurde, so die Kopie des Lambda a ist unabhängig von der externen a Variable.

int a = 5 ; 
auto plus5Val = [a] (void) { return a + 5 ; } ; 
auto plus5Ref = [&a] (void) {return a + 5 ; } ; 

a = 7 ; 
std::cout << a << ", value " << plus5Val() << ", reference " << plus5Ref() ;
// The result will be "7, value 10, reference 12"

Generalisierte Erfassung

C ++ 14

Lambdas können Ausdrücke und nicht nur Variablen erfassen. Auf diese Weise können Lambdas Nur-Bewegung-Typen speichern:

auto p = std::make_unique<T>(...);

auto lamb = [p = std::move(p)]() //Overrides capture-by-value of `p`.
{
  p->SomeFunc();
};

Dadurch wird die äußere p Variable in die Lambda-Capture-Variable, auch als p bezeichnet, p . lamb besitzt nun den von make_unique zugewiesenen make_unique . Da der Abschluss einen Typ enthält, der nicht kopierbar ist, bedeutet dies, dass lamb selbst nicht kopierbar ist. Es kann aber verschoben werden:

auto lamb_copy = lamb; //Illegal
auto lamb_move = std::move(lamb); //legal.

Jetzt besitzt lamb_move die Erinnerung.


Beachten Sie, dass für std::function<> die gespeicherten Werte kopierbar sein müssen. Sie können Ihre eigene move-only-erfordernde std::function schreiben oder das Lambda einfach in einen shared_ptr Wrapper shared_ptr :

auto shared_lambda = [](auto&& f){
  return [spf = std::make_shared<std::decay_t<decltype(f)>>(decltype(f)(f))]
  (auto&&...args)->decltype(auto) {
    return (*spf)(decltype(args)(args)...);
  };
};
auto lamb_shared = shared_lambda(std::move(lamb_move));

nimmt unseren Einzug nur Lambda und stopft seinen Zustand in einen gemeinsamen Zeiger gibt dann eine Lambda, die kopiert werden und dann in einer gespeicherten std::function oder ähnlichem.


Die generalisierte Erfassung verwendet den auto Typabzug für den Variablentyp. Diese Captures werden standardmäßig als Werte deklariert, sie können jedoch auch Referenzen sein:

int a = 0;

auto lamb = [&v = a](int add) //Note that `a` and `v` have different names
{
  v += add; //Modifies `a`
};

lamb(20); //`a` becomes 20.

Die Generalize-Erfassung muss überhaupt keine externe Variable erfassen. Es kann einen beliebigen Ausdruck erfassen:

auto lamb = [p = std::make_unique<T>(...)]()
{
    p->SomeFunc();
}

Dies ist nützlich, um Lambdas beliebige Werte zu geben, die sie speichern und möglicherweise modifizieren können, ohne sie extern zum Lambda deklarieren zu müssen. Das ist natürlich nur sinnvoll, wenn Sie nicht auf diese Variablen zugreifen möchten, nachdem das Lambda seine Arbeit beendet hat.

Nach Referenz erfassen

Wenn Sie dem Namen einer lokalen Variable ein & voranstellen, wird die Variable als Referenz erfasst. Konzeptionell bedeutet dies, dass der Schließungstyp des Lambda eine Referenzvariable hat, die als Referenz auf die entsprechende Variable von außerhalb des Gültigkeitsbereichs des Lambda initialisiert wird. Jede Verwendung der Variablen im Lambda-Body bezieht sich auf die ursprüngliche Variable:

// Declare variable 'a'
int a = 0;

// Declare a lambda which captures 'a' by reference
auto set = [&a]() {
    a = 1;
};

set();
assert(a == 1);

Das Schlüsselwort mutable wird nicht benötigt, da a selbst nicht const .

Erfassen durch Referenz bedeutet natürlich, dass das Lambda nicht dem Umfang der erfassten Variablen entgehen darf . Sie können also Funktionen aufrufen, die eine Funktion übernehmen, aber Sie dürfen keine Funktion aufrufen, die das Lambda über den Bereich Ihrer Referenzen hinaus speichert . Und du darfst das Lambda nicht zurückgeben.

Standarderfassung

Auf lokale Variablen, die nicht explizit in der Erfassungsliste angegeben sind, kann standardmäßig nicht vom Lambda-Body aus zugegriffen werden. Es ist jedoch möglich, durch den Lambda-Body benannte Variablen implizit zu erfassen:

int a = 1;
int b = 2;

// Default capture by value
[=]() { return a + b; }; // OK; a and b are captured by value

// Default capture by reference
[&]() { return a + b; }; // OK; a and b are captured by reference

Die explizite Erfassung kann immer noch neben der impliziten Standarderfassung erfolgen. Die explizite Erfassungsdefinition überschreibt die Standarderfassung:

int a = 0;
int b = 1;

[=, &b]() {
    a = 2; // Illegal; 'a' is capture by value, and lambda is not 'mutable'
    b = 2; // OK; 'b' is captured by reference
};

Generische Lambdas

c ++ 14

Lambda-Funktionen können Argumente beliebigen Typs annehmen. Dadurch kann ein Lambda generischer sein:

auto twice = [](auto x){ return x+x; };

int i = twice(2); // i == 4
std::string s = twice("hello"); // s == "hellohello"

Dies wird in C ++ implementiert, indem der operator() des Abschlusstyps eine Vorlagenfunktion wird. Der folgende Typ hat das gleiche Verhalten wie der obige Lambda-Verschluss:

struct _unique_lambda_type
{
  template<typename T>
  auto operator() (T x) const {return x + x;}
};

Nicht alle Parameter in einem generischen Lambda müssen generisch sein:

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

Hier wird x basierend auf dem ersten Funktionsargument abgeleitet, während y immer int .

Generische Lambdas können auch Argumente als Referenz verwenden, wobei die üblichen Regeln für auto und & . Wenn ein generischer Parameter als auto&& , ist dies eine Weiterleitungsreferenz auf das übergebene Argument und keine rvalue- Referenz :

auto lamb1 = [](int &&x) {return x + 5;};
auto lamb2 = [](auto &&x) {return x + 5;};
int x = 10;
lamb1(x); // Illegal; must use `std::move(x)` for `int&&` parameters.
lamb2(x); // Legal; the type of `x` is deduced as `int&`.

Lambda-Funktionen können variadisch sein und ihre Argumente perfekt weiterleiten:

auto lam = [](auto&&... args){return f(std::forward<decltype(args)>(args)...);};

oder:

auto lam = [](auto&&... args){return f(decltype(args)(args)...);};

was nur "richtig" mit Variablen vom Typ auto&& funktioniert.

Ein guter Grund, generische Lambdas zu verwenden, ist der Besuch der Syntax.

boost::variant<int, double> value;
apply_visitor(value, [&](auto&& e){
  std::cout << e;
});

Hier besuchen wir polymorph. In anderen Zusammenhängen sind die Namen des Typs, den wir übergeben, nicht interessant:

mutex_wrapped<std::ostream&> os = std::cout;
os.write([&](auto&& os){
  os << "hello world\n";
});

Das Wiederholen des Typs von std::ostream& ist hier Rauschen. Es wäre so, als müssten Sie bei jeder Verwendung den Typ einer Variablen erwähnen. Hier schaffen wir einen Besucher, aber keinen polymorphen Besucher; auto wird aus dem gleichen Grund verwendet, in dem Sie auto in einer for(:) Schleife verwenden können.

Konvertierung in Funktionszeiger

Wenn die Capture-Liste eines Lambdas leer ist, wird das Lambda implizit in einen Funktionszeiger konvertiert, der dieselben Argumente verwendet und denselben Rückgabetyp zurückgibt:

auto sorter = [](int lhs, int rhs) -> bool {return lhs < rhs;};

using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter; // implicit conversion

Eine solche Konvertierung kann auch mit einem unären Plus-Operator durchgeführt werden:

func_ptr sorter_func2 = +sorter; // enforce implicit conversion

Das Aufrufen dieses Funktionszeigers verhält sich genauso wie das Aufrufen von operator() auf dem Lambda. Dieser Funktionszeiger ist in keiner Weise von der Existenz des Quell-Lambda-Verschlusses abhängig. Sie kann daher den Lambda-Verschluss überleben.

Diese Funktion ist vor allem für die Verwendung von Lambdas mit APIs hilfreich, die Funktionszeiger anstelle von C ++ - Funktionsobjekten verwenden.

C ++ 14

Die Konvertierung in einen Funktionszeiger ist auch für generische Lambdas mit leerer Erfassungsliste möglich. Bei Bedarf wird ein Vorlagenargumentenabzug verwendet, um die richtige Spezialisierung auszuwählen.

auto sorter = [](auto lhs, auto rhs) { return lhs < rhs; };
using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter;  // deduces int, int
// note however that the following is ambiguous
// func_ptr sorter_func2 = +sorter;

Klasse Lambdas und Capture davon

Ein in einer Member-Funktion einer Klasse ausgewerteter Lambda-Ausdruck ist implizit ein Freund dieser Klasse:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    // definition of a member function
    void Test()
    {
        auto lamb = [](Foo &foo, int val)
        {
            // modification of a private member variable
            foo.i = val;
        };
        
        // lamb is allowed to access a private member, because it is a friend of Foo
        lamb(*this, 30);
    }
};

Ein solches Lambda ist nicht nur ein Freund dieser Klasse, es hat den gleichen Zugriff wie die Klasse, in der es deklariert ist.

Lambdas können this Zeiger erfassen, der die Objektinstanz darstellt, für die die äußere Funktion aufgerufen wurde. Dazu fügen Sie this der Capture-Liste hinzu:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture the this pointer by value
        auto lamb = [this](int val)
        {
            i = val;
        };
        
        lamb(30);
    }
};

Wenn this erfasst wird, kann das Lambda die Namen der enthaltenen Klasse so verwenden, als wäre es in der enthaltenen Klasse. Ein implizites this-> wird also auf solche Mitglieder angewendet.

Beachten Sie, dass this vom Wert erfasst wird, nicht jedoch vom Wert des Typs. Es wird durch den Wert von this erfasst, der ein Zeiger ist . Als solches ist das Lambda nicht besitzt this . Wenn das Lambda-Out die Lebensdauer des Objekts, das es erstellt hat, lebt, kann das Lambda ungültig werden.

Das bedeutet auch , dass das Lambda ändern kann this ohne erklärt zu werden mutable . Es ist der Zeiger, der const , nicht das Objekt, auf das gezeigt wird. Das heißt, es sei denn, die äußere Elementfunktion war selbst eine const Funktion.

Beachten Sie außerdem, dass die Standard - Capture - Klauseln, die beide [=] und [&] , wird auch erfassen this implizit. Und beide erfassen es durch den Wert des Zeigers. Es ist in der Tat ein Fehler, this in der Capture-Liste anzugeben, wenn ein Standardwert angegeben wird.

C ++ 17

Lambda-Dateien können eine Kopie this Objekts erfassen, das zum Zeitpunkt der Lambda-Erstellung erstellt wurde. Dies geschieht durch Hinzufügen von *this zur Aufnahmeliste:

class Foo
{
private:
    int i;
    
public:
    Foo(int val) : i(val) {}
    
    void Test()
    {
        // capture a copy of the object given by the this pointer
        auto lamb = [*this](int val) mutable
        {
            i = val;
        };
        
        lamb(30); // does not change this->i
    }
};

Übertragen von Lambda-Funktionen nach C ++ 03 mithilfe von Funktoren

Lambda-Funktionen in C ++ sind syntaktischer Zucker, der eine sehr kurze Syntax für das Schreiben von Funktoren bietet. Daher kann eine äquivalente Funktionalität in C ++ 03 erhalten werden (wenn auch viel ausführlicher), indem die Lambda-Funktion in einen Funktor konvertiert wird:

// Some dummy types:
struct T1 {int dummy;};
struct T2 {int dummy;};
struct R {int dummy;};

// Code using a lambda function (requires C++11)
R use_lambda(T1 val, T2 ref) {
  // Use auto because the type of the lambda is unknown.
  auto lambda = [val, &ref](int arg1, int arg2) -> R {
    /* lambda-body */
    return R();
  };
  return lambda(12, 27);
}

// The functor class (valid C++03)
// Similar to what the compiler generates for the lambda function.
class Functor {
  // Capture list.
  T1 val;
  T2& ref;

public:
  // Constructor
  inline Functor(T1 val, T2& ref) : val(val), ref(ref) {}

  // Functor body
  R operator()(int arg1, int arg2) const {
    /* lambda-body */
    return R();
  }
};

// Equivalent to use_lambda, but uses a functor (valid C++03).
R use_functor(T1 val, T2 ref) {
  Functor functor(val, ref);
  return functor(12, 27);
}

// Make this a self-contained example.
int main() {
  T1 t1;
  T2 t2;
  use_functor(t1,t2);
  use_lambda(t1,t2);
  return 0;
}

Wenn die Lambda-Funktion mutable ist, machen Sie den Aufrufoperator des Funkers zu non-const, dh:

R operator()(int arg1, int arg2) /*non-const*/ {
  /* lambda-body */
  return R();
}

Rekursive Lambdas

Nehmen wir an, wir möchten Euclids gcd() als Lambda schreiben. Als Funktion ist es:

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a%b);
}

Ein Lambda kann jedoch nicht rekursiv sein, es gibt keine Möglichkeit, sich selbst anzurufen. Ein Lambda hat keinen Namen und die Verwendung this in den Körper eines Lambda bezieht sich auf ein erfasstes this (die Lambda unter der Annahme , wird im Körper einer Elementfunktion geschaffen, sonst ist es ein Fehler). Wie lösen wir dieses Problem?

Verwenden Sie die std::function

Wir können eine Lambda-Capture-Referenz zu einer noch nicht erstellten std::function :

std::function<int(int, int)> gcd = [&](int a, int b){
    return b == 0 ? a : gcd(b, a%b);
};

Das funktioniert, sollte aber sparsam eingesetzt werden. Es ist langsam (wir verwenden jetzt Typenlöschung anstelle eines direkten Funktionsaufrufs), es ist fragil (das Kopieren von gcd oder das Zurückgeben von gcd wird gcd , da das Lambda auf das ursprüngliche Objekt verweist), und es funktioniert nicht mit generischen Lambdas.

Mit zwei intelligenten Zeigern:

auto gcd_self = std::make_shared<std::unique_ptr< std::function<int(int, int)> >>();
*gcd_self = std::make_unique<std::function<int(int, int)>>(
  [gcd_self](int a, int b){
    return b == 0 ? a : (**gcd_self)(b, a%b);
  };
};

Dies führt zu einer erheblichen Anzahl von Umleitungen (was Overhead ist), aber es kann kopiert / zurückgegeben werden, und alle Kopien teilen sich den Status. Es erlaubt Ihnen, das Lambda zurückzugeben, und ist sonst weniger zerbrechlich als die obige Lösung.

Verwenden Sie einen Y-Kombinator

Mit Hilfe einer kurzen Utility-Struktur können wir all diese Probleme lösen:

template <class F>
struct y_combinator {
    F f; // the lambda will be stored here

    // a forwarding operator():
    template <class... Args>
    decltype(auto) operator()(Args&&... args) const {
        // we pass ourselves to f, then the arguments.
        // the lambda should take the first argument as `auto&& recurse` or similar.
        return f(*this, std::forward<Args>(args)...);
    }
};
// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
    return {std::forward<F>(f)};
}
// (Be aware that in C++17 we can do better than a `make_` function)

wir können unsere gcd wie gcd implementieren:

auto gcd = make_y_combinator(
  [](auto&& gcd, int a, int b){
    return b == 0 ? a : gcd(b, a%b);
  }
);

Der y_combinator ist ein Konzept aus dem Lambda-Kalkül, mit dem Sie eine Rekursion haben können, ohne sich selbst benennen zu können, bis Sie definiert sind. Das ist genau das Problem, das Lambdas haben.

Sie erstellen ein Lambda, das als erstes Argument "recurse" verwendet. Wenn Sie rekursieren möchten, übergeben Sie die Argumente, um erneut aufzurufen.

Der y_combinator dann ein Funktionsobjekt zurück, das diese Funktion mit seinen Argumenten aufruft, jedoch mit einem geeigneten "recurse" -Objekt (nämlich dem y_combinator selbst) als erstes Argument. Die übrigen Argumente, mit denen Sie den y_combinator aufrufen, werden y_combinator an das Lambda weitergeleitet.

Zusamenfassend:

auto foo = make_y_combinator( [&](auto&& recurse, some arguments) {
  // write body that processes some arguments
  // when you want to recurse, call recurse(some other arguments)
});

und Sie haben eine Rekursion in einem Lambda ohne gravierende Einschränkungen oder erheblichen Mehraufwand.

Verwenden von Lambdas zum Auspacken des Inline-Parameterpakets

C ++ 14

Das Auspacken von Parametern erfordert normalerweise das Schreiben einer Hilfsfunktion für jedes Mal, wenn Sie dies tun möchten.

In diesem Spielzeugbeispiel:

template<std::size_t...Is>
void print_indexes( std::index_sequence<Is...> ) {
  using discard=int[];
  (void)discard{0,((void)(
    std::cout << Is << '\n' // here Is is a compile-time constant.
  ),0)...};
}
template<std::size_t I>
void print_indexes_upto() {
  return print_indexes( std::make_index_sequence<I>{} );
}

Der print_indexes_upto möchte ein Parameterpaket mit Indizes erstellen und auspacken. Dazu muss eine Hilfsfunktion aufgerufen werden. Jedes Mal, wenn Sie ein von Ihnen erstelltes Parameterpaket auspacken möchten, müssen Sie dazu eine benutzerdefinierte Hilfsfunktion erstellen.

Dies kann mit Lambdas vermieden werden.

Sie können Parameterpakete wie folgt in eine Reihe von Aufrufen eines Lambda entpacken:

template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    using discard=int[];
    (void)discard{0,(void(
      f( index<Is> )
    ),0)...};
  };
}

template<std::size_t N>
auto index_over(index_t<N> = {}) {
  return index_over( std::make_index_sequence<N>{} );
}
C ++ 17

Mit fold-Ausdrücken kann index_over() folgendermaßen vereinfacht werden:

template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
  return [](auto&& f){
    ((void)(f(index<Is>)), ...);
  };
}

Sobald Sie dies getan haben, können Sie dies verwenden, um das manuelle Auspacken von Parameterpaketen durch eine zweite Überladung in anderem Code zu ersetzen, sodass Sie die Parameterpakete "inline" entpacken können:

template<class Tup, class F>
void for_each_tuple_element(Tup&& tup, F&& f) {
  using T = std::remove_reference_t<Tup>;
  using std::tuple_size;
  auto from_zero_to_N = index_over< tuple_size<T>{} >();

  from_zero_to_N(
    [&](auto i){
      using std::get;
      f( get<i>( std::forward<Tup>(tup) ) );
    }
  );
}

Das auto i durch das index_over an das Lambda index_over ist std::integral_constant<std::size_t, ???> . Dies hat eine constexpr Konvertierung in std::size_t , die nicht vom Zustand this constexpr abhängt. constexpr können wir es als Kompilierungszeitkonstante verwenden, z. B. wenn wir es an std::get<i> .

Um zum Spielzeugbeispiel oben zurückzukehren, schreiben Sie es wie folgt um:

template<std::size_t I>
void print_indexes_upto() {
  index_over(index<I>)([](auto i){
    std::cout << i << '\n'; // here i is a compile-time constant
  });
}

Das ist viel kürzer und behält die Logik in dem Code, der es verwendet.

Live-Beispiel zum Spielen.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow