Namespaces
Variants

Lambda expressions (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Konstruiert einen Closure (ein unbenanntes Funktionsobjekt, das Variablen im Gültigkeitsbereich erfassen kann).

Inhaltsverzeichnis

Syntax

Lambda-Ausdrücke ohne explizite Template-Parameterliste (möglicherweise nicht generisch)
[ Erfassungen  ] front-attr  (optional) ( Parameter  ) Spezifikationen  (optional) Ausnahme  (optional)
back-attr  (optional) Nachstellung  (optional) Anforderungen  (optional) Vertragsspezifikationen  (optional) { Rumpf }
(1)
[ Erfassungen  ] { Rumpf } (2) (bis C++23)
[ Erfassungen  ] front-attr  (optional) Nachstellung  (optional) Vertragsspezifikationen  (optional) { Rumpf } (2) (seit C++23)
[ Erfassungen  ] front-attr  (optional) Ausnahme
back-attr  (optional) Nachstellung  (optional) Vertragsspezifikationen  (optional) { Rumpf }
(3) (seit C++23)
[ Erfassungen  ] front-attr  (optional) Spezifikationen Ausnahme  (optional)
back-attr  (optional) Nachstellung  (optional) Vertragsspezifikationen  (optional) { Rumpf }
(4) (seit C++23)
Lambda-Ausdrücke mit einer expliziten Template-Parameterliste (immer generisch) (seit C++20)
[ Erfassungen  ] < tparams  > t-requires  (optional)
front-attr  (optional) ( Parameter  ) Spezifikationen  (optional) Ausnahme  (optional)
back-attr  (optional) Nachstellung  (optional) requires  (optional) Vertragsspezifikationen  (optional) { Rumpf }
(1)
[ Erfassungen  ] < tparams  > t-requires  (optional) { Rumpf } (2) (bis C++23)
[ Erfassungen  ] < tparams  > t-requires  (optional)
front-attr  (optional) Nachstellung  (optional) Vertragsspezifikationen  (optional) { Rumpf }
(2) (seit C++23)
[ Erfassungen  ] < tparams  > t-requires  (optional) front-attr  (optional) Ausnahme
back-attr  (optional) Nachstellung  (optional) Vertragsspezifikationen  (optional) { Rumpf }
(3) (seit C++23)
[ Erfassungen  ] < tparams  > t-requires  (optional) front-attr  (optional) Spezifikationen Ausnahme  (optional)
back-attr  (optional) Nachstellung  (optional) Vertragsspezifikationen  (optional) { Rumpf }
(4) (seit C++23)
1) Der Lambda-Ausdruck mit einer Parameterliste.
2-4) Der Lambda-Ausdruck ohne eine Parameterliste.
2) Die einfachste Syntax. back-attr kann nicht angewendet werden.
3,4) back-attr kann nur angewendet werden, wenn entweder specs oder except vorhanden ist.

Erklärung

captures - Gibt die Entitäten an, die erfasst werden sollen.
tparams - Eine nicht-leere, kommagetrennte Liste von Template-Parametern , die verwendet wird, um den Template-Parametern eines generischen Lambda-Ausdrucks Namen zuzuweisen (siehe ClosureType::operator() unten).
t-requires - Fügt constraints zu tparams hinzu.

Wenn t-requires mit einer Attributspezifizierer-Sequenz endet, werden die Attribute in der Sequenz als Attribute in front-attr behandelt.

(since C++23)
front-attr - (since C++23) Eine Attributspezifizierer-Sequenz gilt für operator ( ) des Closure-Typs (und somit kann das [[ noreturn ]] Attribut verwendet werden).
params - Die Parameterliste des operator ( ) des Closure-Typs.

Sie kann einen expliziten Objektparameter enthalten.

(since C++23)
specs - Eine Liste der folgenden Spezifizierer, jeder Spezifizierer ist in jeder Sequenz höchstens einmal erlaubt.
Spezifizierer Wirkung
mutable Erlaubt es dem body , die durch Kopie erfassten Objekte zu modifizieren und deren nicht-konstante Memberfunktionen aufzurufen.
  • Kann nicht verwendet werden, wenn ein expliziter Objektparameter vorhanden ist.
(seit C++23)
constexpr
(seit C++17)
Spezifiziert explizit, dass operator ( ) eine constexpr-Funktion ist.
  • Wenn operator ( ) alle Anforderungen an constexpr-Funktionen erfüllt, wird operator ( ) constexpr sein, selbst wenn constexpr nicht vorhanden ist.
consteval
(seit C++20)
Spezifiziert, dass operator ( ) eine Immediate-Funktion ist.
  • consteval und constexpr können nicht gleichzeitig angegeben werden.
static
(seit C++23)
Spezifiziert, dass operator ( ) eine statische Memberfunktion ist.
  • static und mutable können nicht gleichzeitig angegeben werden.
  • Kann nicht verwendet werden, wenn captures nicht leer ist oder ein expliziter Objektparameter vorhanden ist.
except - Stellt die dynamic exception specification oder (bis C++20) den noexcept specifier für operator ( ) des Closure-Typs bereit.
back-attr - Eine Attributspezifizierer-Sequenz gilt für den Typ von operator ( ) des Closure-Typs (daher kann das [[ noreturn ]] Attribut nicht verwendet werden).
trailing - -> ret , wobei ret den Rückgabetyp angibt.
requires - (since C++20) Fügt constraints zum operator ( ) des Closure-Typs hinzu.
contract-specs - (since C++26) Eine Liste von Funktionsvertragsspezifizierern für den operator ( ) des Closure-Typs.
body - Der Funktionskörper.


Wenn auto als Typ eines Parameters verwendet wird oder eine explizite Template-Parameterliste bereitgestellt wird (seit C++20) , ist das Lambda ein generisches Lambda .

(seit C++14)

Eine Variable __func__ wird implizit am Anfang des Funktionskörpers definiert, mit der Semantik wie hier beschrieben.

Closure-Typ

Der Lambda-Ausdruck ist ein prvalue-Ausdruck eines eindeutigen unbenannten Nicht- union Nicht- aggregate Klassentyps, bekannt als closure type , der (für die Zwecke von ADL ) im kleinsten Blockbereich, Klassenbereich oder Namensraumbereich deklariert wird, der den Lambda-Ausdruck enthält.

Der Closure-Typ ist ein struktureller Typ genau dann, wenn captures leer ist.

(since C++20)

Der Closure-Typ hat die folgenden Member, sie können nicht explizit instanziiert , explizit spezialisiert , oder (seit C++14) in einer Friend-Deklaration benannt werden:

ClosureType:: operator()( params )

ret operator ( ) ( params ) { body }
(static und const können vorhanden sein, siehe unten)
template < template - params >
ret operator ( ) ( params ) { body }
(seit C++14)
(generisches Lambda, static und const können vorhanden sein, siehe unten)

Führt den Körper des Lambda-Ausdrucks aus, wenn er aufgerufen wird. Beim Zugriff auf eine Variable wird auf ihre erfasste Kopie zugegriffen (für die durch Kopie erfassten Entitäten) oder auf das ursprüngliche Objekt (für die durch Referenz erfassten Entitäten).

Die Parameterliste von operator ( ) ist params falls angegeben, andernfalls ist die Parameterliste leer.

Der Rückgabetyp von operator ( ) ist der Typ, der im trailing  angegeben ist.

Wenn trailing nicht angegeben wird, wird der Rückgabetyp von operator ( ) automatisch deduziert . [1]

Sofern das Schlüsselwort mutable nicht in den Lambda-Spezifizierern verwendet wurde oder ein expliziter Objektparameter vorhanden ist (seit C++23) , ist der CV-Qualifizierer von operator ( ) const und die durch Kopie erfassten Objekte sind innerhalb dieses operator ( ) nicht veränderbar. Explizite const Qualifizierer sind nicht zulässig. operator ( ) ist niemals virtual und kann nicht den volatile Qualifizierer besitzen.

operator ( ) ist immer constexpr, wenn es die Anforderungen einer constexpr-Funktion erfüllt. Es ist ebenfalls constexpr, wenn das Schlüsselwort constexpr in den Lambda-Spezifizierern verwendet wurde.

(seit C++17)

operator ( ) ist eine Immediate-Funktion , wenn das Schlüsselwort consteval in den Lambda-Spezifizierern verwendet wurde.

(seit C++20)

operator ( ) ist eine statische Memberfunktion , wenn das Schlüsselwort static in den Lambda-Spezifizierern verwendet wurde.

operator ( ) ist eine explizite Objekt-Memberfunktion , wenn params einen expliziten Objektparameter enthält.

(seit C++23)


Für jeden Parameter in params , dessen Typ als auto spezifiziert ist, wird ein erfundener Template-Parameter zu template-params in der Reihenfolge des Erscheinens hinzugefügt. Der erfundene Template-Parameter kann ein Parameter Pack sein, falls das entsprechende Funktionsmitglied von params ein Funktionsparameter-Pack ist.

// generic lambda, operator() is a template with two parameters
auto glambda = [](auto a, auto&& b) { return a < b; };
bool b = glambda(3, 3.14); // OK
// generic lambda, operator() is a template with one parameter
auto vglambda = [](auto printer)
{
    return [=](auto&&... ts) // generic lambda, ts is a parameter pack
    { 
        printer(std::forward<decltype(ts)>(ts)...);
        // nullary lambda (takes no parameters):
        return [=] { printer(ts...); };
    };
};
auto p = vglambda([](auto v1, auto v2, auto v3)
{
    std::cout << v1 << v2 << v3;
});
auto q = p(1, 'a', 3.14); // outputs 1a3.14
q();                      // outputs 1a3.14
(seit C++14)


Falls die Lambda-Definition eine explizite Template-Parameterliste verwendet, wird diese Template-Parameterliste mit operator ( ) verwendet. Für jeden Parameter in params , dessen Typ als auto spezifiziert ist, wird ein zusätzlicher erfundener Template-Parameter an das Ende dieser Template-Parameterliste angehängt:

// generic lambda, operator() is a template with two parameters
auto glambda = []<class T>(T a, auto&& b) { return a < b; };
// generic lambda, operator() is a template with one parameter pack
auto f = []<typename... Ts>(Ts&&... ts)
{
    return foo(std::forward<Ts>(ts)...);
};
(seit C++20)

Die Ausnahmespezifikation except auf dem Lambda-Ausdruck gilt für operator ( ) .

Für den Zweck der Namenssuche , der Bestimmung des Typs und Werts des this Pointers und für den Zugriff auf nicht-statische Klassenmember wird der Body des Closure-Typs operator ( ) im Kontext des Lambda-Ausdrucks betrachtet.

struct X
{
    int x, y;
    int operator()(int);
    void f()
    {
        // Der Kontext des folgenden Lambda-Ausdrucks ist die Memberfunktion X::f
        [=]() -> int
        {
            return operator()(this->x + y); // X::operator()(this->x + (*this).y)
                                            // this hat den Typ X*
        };
    }
};

Hängende Referenzen

Wenn eine Nicht-Referenz-Entität implizit oder explizit als Referenz erfasst wird und der operator ( ) des Closure-Objekts aufgerufen wird, nachdem die Lebensdauer der Entität beendet wurde, tritt undefiniertes Verhalten auf. Die C++-Closures verlängern nicht die Lebensdauer von als Referenz erfassten Objekten.

Gleiches gilt für die Lebensdauer des aktuellen * this Objekts, das über this erfasst wird.

  1. Obwohl die Funktionsrückgabetyp-Deduktion in C++14 eingeführt wurde, ist ihre Regel für die Lambda-Rückgabetyp-Deduktion in C++11 verfügbar.

ClosureType:: operator ret (*)( params )()

capture-less non-generic lambda
using F = ret ( * ) ( params ) ;
operator F ( ) const noexcept ;
(bis C++17)
using F = ret ( * ) ( params ) ;
constexpr operator F ( ) const noexcept ;
(seit C++17)
capture-less generic lambda
template < template - params > using fptr_t = /* siehe unten */ ;

template < template - params >

operator fptr_t < template - params > ( ) const noexcept ;
(seit C++14)
(bis C++17)
template < template - params > using fptr_t = /* siehe unten */ ;

template < template - params >

constexpr operator fptr_t < template - params > ( ) const noexcept ;
(seit C++17)

Diese benutzerdefinierte Konvertierungsfunktion ist nur definiert, wenn der Lambda-Ausdruck keine Captures  und keinen expliziten Objektparameter hat (seit C++23) . Es handelt sich um eine öffentliche, constexpr, (seit C++17) nicht-virtuelle, nicht-explizite, const noexcept-Memberfunktion des Closure-Objekts.

Diese Funktion ist eine Immediate Function , wenn der Funktionsaufrufoperator (oder die Spezialisierung, für generische Lambdas) eine Immediate Function ist.

(seit C++20)

Eine generische Capture-Less-Lambda hat eine benutzerdefinierte Konvertierungsfunktionsvorlage mit derselben erfundenen Template-Parameterliste wie operator ( ) .

void f1(int (*)(int)) {}
void f2(char (*)(int)) {}
void h(int (*)(int)) {}  // #1
void h(char (*)(int)) {} // #2
auto glambda = [](auto a) { return a; };
f1(glambda); // OK
f2(glambda); // Fehler: nicht konvertierbar
h(glambda);  // OK: ruft #1 auf, da #2 nicht konvertierbar ist
int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK
(seit C++14)


Der von der Konvertierungsfunktion zurückgegebene Wert ist ein Zeiger auf eine Funktion mit C++ language linkage , deren Aufruf denselben Effekt hat wie der Aufruf des Funktionsaufrufoperators des Closure-Typs auf einer default-konstruierten Instanz des Closure-Typs.

(bis C++14)

Der von der Konvertierungsfunktion (-template) zurückgegebene Wert ist ein Zeiger auf eine Funktion mit C++ language linkage , deren Aufruf denselben Effekt hat wie:

  • für nicht-generische Lambdas: Aufruf des operator ( ) des Closure-Typs auf einer default-konstruierten Instanz des Closure-Typs.
  • für generische Lambdas: Aufruf der entsprechenden operator ( ) -Spezialisierung des generischen Lambda auf einer default-konstruierten Instanz des Closure-Typs.
(seit C++14)
(bis C++23)

Der von der Konvertierungsfunktion (-template) zurückgegebene Wert ist

  • falls operator ( ) static ist, ein Zeiger auf diesen operator ( ) mit C++ language linkage ,
  • andernfalls ein Zeiger auf eine Funktion mit C++ language linkage , deren Aufruf denselben Effekt hat wie:
    • für nicht-generische Lambdas: Aufruf des operator ( ) des Closure-Typs auf einer default-konstruierten Instanz des Closure-Typs.
    • für generische Lambdas: Aufruf der entsprechenden operator ( ) -Spezialisierung des generischen Lambda auf einer default-konstruierten Instanz des Closure-Typs.
(seit C++23)


Diese Funktion ist constexpr, wenn der Funktionsaufrufoperator (oder die Spezialisierung, für generische Lambdas) constexpr ist.

auto Fwd = [](int(*fp)(int), auto a) { return fp(a); };
auto C = [](auto a) { return a; };
static_assert(Fwd(C, 3) == 3);  // OK
auto NC = [](auto a) { static int s; return a; };
static_assert(Fwd(NC, 3) == 3); // error: no specialization can be
                                // constexpr because of static s

Wenn der operator ( ) des Closure-Objekts eine nicht-werfende Ausnahmespezifikation hat, dann hat der von dieser Funktion zurückgegebene Zeiger den Typ Zeiger auf noexcept-Funktion.

(seit C++17)

ClosureType:: ClosureType()

ClosureType ( ) = default ;
(seit C++20)
(nur wenn keine Captures angegeben sind)
ClosureType ( const ClosureType & ) = default ;
ClosureType ( ClosureType && ) = default ;

Closure-Typen sind nicht DefaultConstructible . Closure-Typen haben keinen Standardkonstruktor.

(bis C++20)

Wenn keine Captures angegeben sind, hat der Closure-Typ einen standardmäßigen Standardkonstruktor. Andernfalls hat er keinen Standardkonstruktor (dies schließt den Fall ein, wenn es ein Capture-Default  gibt, selbst wenn es tatsächlich nichts captured).

(seit C++20)

Der Kopierkonstruktor und der Move-Konstruktor werden als standardmäßig deklariert und können gemäß den üblichen Regeln für Kopierkonstruktoren und Move-Konstruktoren implizit definiert werden.

ClosureType:: operator=(const ClosureType&)

ClosureType & operator = ( const ClosureType & ) = delete ;
(bis C++20)
ClosureType & operator = ( const ClosureType & ) = default ;
ClosureType & operator = ( ClosureType && ) = default ;
(seit C++20)
(nur wenn keine Captures angegeben sind)
ClosureType & operator = ( const ClosureType & ) = delete ;
(seit C++20)
(ansonsten)

Der Kopierzuweisungsoperator ist als gelöscht definiert (und der Verschiebungszuweisungsoperator ist nicht deklariert). Closure-Typen sind nicht CopyAssignable .

(bis C++20)

Wenn keine Captures angegeben sind, besitzt der Closure-Typ einen standardmäßigen Kopierzuweisungsoperator und einen standardmäßigen Verschiebungszuweisungsoperator. Andernfalls besitzt er einen gelöschten Kopierzuweisungsoperator (dies schließt den Fall ein, wenn ein Capture-Default  vorhanden ist, selbst wenn dieser tatsächlich nichts captured).

(seit C++20)

ClosureType:: ~ClosureType()

~ClosureType ( ) = default ;

Der Destruktor ist implizit deklariert.

ClosureType:: Captures

T1 a ;

T2 b ;

...

Wenn der Lambda-Ausdruck etwas durch Kopie erfasst (entweder implizit mit der Erfassungsklausel [=] oder explizit mit einer Erfassung, die das Zeichen & nicht enthält, z.B. [a, b, c] ), enthält der Closure-Typ unbenannte nicht-statische Datenelemente, die in nicht spezifizierter Reihenfolge deklariert sind und Kopien aller so erfassten Entitäten halten.

Jene Datenelemente, die Erfassungen ohne Initialisierer entsprechen, werden direkt initialisiert , wenn der Lambda-Ausdruck ausgewertet wird. Jene, die Erfassungen mit Initialisierern entsprechen, werden entsprechend den Anforderungen des Initialisierers initialisiert (könnte Kopie- oder Direktinitialisierung sein). Wenn ein Array erfasst wird, werden Array-Elemente in aufsteigender Indexreihenfolge direkt initialisiert. Die Reihenfolge, in der die Datenelemente initialisiert werden, ist die Reihenfolge ihrer Deklaration (die nicht spezifiziert ist).

Der Typ jedes Datenelements ist der Typ der entsprechenden erfassten Entität, außer wenn die Entität Referenztyp hat (in diesem Fall werden Referenzen auf Funktionen als Lvalue-Referenzen auf die referenzierten Funktionen erfasst, und Referenzen auf Objekte werden als Kopien der referenzierten Objekte erfasst).

Für Entitäten, die durch Referenz erfasst werden (mit dem capture-default [&] oder bei Verwendung des Zeichens &, z.B. [&a, &b, &c] ), ist es nicht spezifiziert, ob zusätzliche Datenelemente im Closure-Typ deklariert werden , aber alle solche zusätzlichen Elemente müssen LiteralType erfüllen (seit C++17) .


Lambda-Ausdrücke sind nicht erlaubt in unevaluated expressions , template arguments , alias declarations , typedef declarations , und überall in einer Funktions- (oder Funktions-Template-) Deklaration außer dem Funktionskörper und den default arguments der Funktion.

(bis C++20)

Lambda-Capture

Die captures definieren die externen Variablen, die innerhalb des Lambda-Funktionskörpers zugänglich sind. Ihre Syntax wird wie folgt definiert:

capture-default (1)
capture-list (2)
capture-default , capture-list (3)
capture-default - einer von & und =
capture-list - eine kommagetrennte Liste von capture s


Die Syntax von capture ist wie folgt definiert:

Bezeichner (1)
Bezeichner ... (2)
Bezeichner Initialisierer (3) (seit C++14)
& Bezeichner (4)
& Bezeichner ... (5)
& Bezeichner Initialisierer (6) (seit C++14)
this (7)
* this (8) (seit C++17)
... Bezeichner Initialisierer (9) (seit C++20)
& ... Bezeichner Initialisierer (10) (seit C++20)
1) einfache Kopiererfassung
2) einfache By-Copy-Erfassung, die eine Pack-Erweiterung ist
3) Kopier-Erfassung mit einem Initialisierer
4) einfacher By-Reference-Capture
5) einfacher By-Reference-Capture, der eine Pack-Erweiterung ist
6) By-Reference-Erfassung mit einem Initialisierer
7) einfacher By-Reference-Capture des aktuellen Objekts
8) einfache Kopiererfassung des aktuellen Objekts
9) By-Copy-Erfassung mit einem Initialisierer, der eine Paketentwicklung ist
10) By-Reference-Erfassung mit einem Initialisierer, der eine Pack-Erweiterung ist

Wenn der capture-default als & festgelegt ist, dürfen nachfolgende einfache Captures nicht mit & beginnen.

struct S2 { void f(int i); };
void S2::f(int i)
{
    [&] {};          // OK: Standardmäßige Erfassung per Referenz
    [&, i] {};       // OK: Erfassung per Referenz, außer i wird als Kopie erfasst
    [&, &i] {};      // Fehler: Erfassung per Referenz, wenn Referenz bereits Standard ist
    [&, this] {};    // OK, äquivalent zu [&]
    [&, this, i] {}; // OK, äquivalent zu [&, i]
}

Wenn die capture-default = ist, müssen nachfolgende einfache Captures mit & beginnen oder *this sein (seit C++17) oder this sein (seit C++20) .

struct S2 { void f(int i); };
void S2::f(int i)
{
    [=] {};        // OK: Standardmäßige Kopiererfassung
    [=, &i] {};    // OK: Kopiererfassung, außer i wird per Referenz erfasst
    [=, *this] {}; // bis C++17: Fehler: Ungültige Syntax
                   // seit C++17: OK: Erfasst das umschließende S2 durch Kopie
    [=, this] {};  // bis C++20: Fehler: this bei = als Standard
                   // seit C++20: OK, identisch mit [=]
}

Jede Erfassung darf nur einmal erscheinen und ihr Name muss sich von jedem Parameternamen unterscheiden:

struct S2 { void f(int i); };
void S2::f(int i)
{
    [i, i] {};        // Fehler: i wiederholt
    [this, *this] {}; // Fehler: "this" wiederholt (C++17)
    [i] (int i) {};   // Fehler: Parameter und Capture haben denselben Namen
}

Ein Lambda-Ausdruck kann eine Variable verwenden, ohne sie zu erfassen, wenn die Variable

  • ist eine nicht-lokale Variable oder hat eine statische oder thread-lokale Speicherdauer (in diesem Fall kann die Variable nicht erfasst werden), oder
  • ist eine Referenz, die mit einem konstanten Ausdruck initialisiert wurde.

Ein Lambda-Ausdruck kann den Wert einer Variable lesen, ohne sie zu erfassen, wenn die Variable

  • hat einen const nicht-flüchtigen integralen oder Aufzählungstyp und wurde mit einem constant expression initialisiert, oder
  • ist constexpr und hat keine mutable-Member.

Das aktuelle Objekt ( * this ) kann implizit erfasst werden, wenn eine Standarderfassung vorhanden ist. Wenn es implizit erfasst wird, wird es immer als Referenz erfasst, selbst wenn die Standarderfassung = ist. Die implizite Erfassung von * this bei einer Standarderfassung von = ist veraltet. (seit C++20)

Nur Lambda-Ausdrücke, die eine der folgenden Bedingungen erfüllen, dürfen eine capture-default oder capture ohne Initialisierer haben:

(seit C++26)

Für einen solchen Lambda-Ausdruck ist der reaching scope definiert als die Menge der umschließenden Gültigkeitsbereiche bis einschließlich des innersten umschließenden Funktionsbereichs (und seiner Parameter). Dies umfasst verschachtelte Blockgültigkeitsbereiche und die Gültigkeitsbereiche umschließender Lambdas, falls dieser Lambda-Ausdruck verschachtelt ist.

Der Bezeichner in jeder Erfassung ohne Initialisierer (außer der this -Erfassung) wird mittels gewöhnlicher unqualifizierter Namenssuche im erreichbaren Gültigkeitsbereich des Lambda-Ausdrucks gesucht. Das Ergebnis der Suche muss eine Variable mit automatischer Speicherdauer sein, die im erreichbaren Gültigkeitsbereich deklariert ist , oder eine strukturierte Bindung , deren entsprechende Variable diese Anforderungen erfüllt (seit C++20) . Die Entität wird explizit erfasst .

Eine Erfassung mit einem Initialisierer, genannt Init-Capture , verhält sich so, als ob sie eine Variable deklariert und explizit erfasst, die mit dem Typbezeichner auto und demselben Initialisierer deklariert wurde, deren deklarativer Bereich der Körper des Lambda-Ausdrucks ist (das heißt, sie ist nicht im Gültigkeitsbereich innerhalb ihres Initialisierers), außer dass:

  • wenn die Erfassung per Kopie erfolgt, das eingeführte nicht-statische Datenelement des Closure-Objekts eine andere Möglichkeit ist, auf diese Variable zu verweisen;
    • mit anderen Worten, die Quellvariable existiert nicht wirklich, und die Typableitung über auto und die Initialisierung werden auf das nicht-statische Datenelement angewendet;
  • wenn die Erfassung per Referenz erfolgt, endet die Lebensdauer der Referenzvariablen, wenn die Lebensdauer des Closure-Objekts endet.

Dies wird verwendet, um nur-verschiebbare Typen mit einer Erfassung wie x = std :: move ( x ) zu erfassen.

Dies ermöglicht auch die Erfassung per const-Referenz, mit & cr = std:: as_const ( x ) oder ähnlichem.

int x = 4;
auto y = [&r = x, x = x + 1]() -> int
{
    r += 2;
    return x * x;
}(); // updates ::x to 6 and initializes y to 25.
(since C++14)

Wenn captures einen capture-default hat und das umschließende Objekt nicht explizit erfasst (als this oder * this ), oder eine automatische Variable, die odr-verwendbar im Lambda-Rumpf ist , oder eine strukturierte Bindung , deren entsprechende Variable atomaren Speicher besitzt (seit C++20) , erfasst es die Entität implizit , wenn die Entität in einem potenziell ausgewerteten Ausdruck innerhalb eines Ausdrucks genannt wird (einschließlich wenn das implizite this - > vor einer Verwendung eines nicht-statischen Klassenmembers hinzugefügt wird).

Zum Zweck der Bestimmung impliziter Erfassungen wird typeid niemals als eine Operation betrachtet, die ihre Operanden als nicht ausgewertet behandelt.

Entitäten können implizit erfasst werden, selbst wenn sie nur innerhalb einer verworfenen Anweisung nach der Instanziierung des Lambda-Rumpfs benannt werden.

(since C++17)
void f(int, const int (&)[2] = {}) {}   // #1
void f(const int&, const int (&)[1]) {} // #2
struct NoncopyableLiteralType
{
    constexpr explicit NoncopyableLiteralType(int n) : n_(n) {}
    NoncopyableLiteralType(const NoncopyableLiteralType&) = delete;
    int n_;
};
void test()
{
    const int x = 17;
    auto l0 = []{ f(x); };           // OK: ruft #1 auf, erfasst x nicht
    auto g0 = [](auto a) { f(x); };  // wie oben
    auto l1 = [=]{ f(x); };          // OK: erfasst x (seit P0588R1) und ruft #1 auf
                                     // die Erfassung kann wegoptimiert werden
    auto g1 = [=](auto a) { f(x); }; // wie oben
    auto ltid = [=]{ typeid(x); };   // OK: erfasst x (seit P0588R1)
                                     // obwohl x nicht ausgewertet wird
                                     // die Erfassung kann wegoptimiert werden
    auto g2 = [=](auto a)
    {
        int selector[sizeof(a) == 1 ? 1 : 2] = {};
        f(x, selector); // OK: ist ein abhängiger Ausdruck, erfasst also x
    };
    auto g3 = [=](auto a)
    {
        typeid(a + x);  // erfasst x unabhängig davon,
                        // ob a + x ein nicht ausgewerteter Operand ist
    };
    constexpr NoncopyableLiteralType w{42};
    auto l4 = []{ return w.n_; };      // OK: w wird nicht odr-verwendet, Erfassung unnötig
    // auto l5 = [=]{ return w.n_; };  // Fehler: w muss per Kopie erfasst werden
}

Wenn der Rumpf eines Lambda-Ausdrucks odr-verwendet eine durch Kopie erfasste Entität, wird auf das Mitglied des Closure-Typs zugegriffen. Wenn die Entität nicht odr-verwendet wird, erfolgt der Zugriff auf das ursprüngliche Objekt:

void f(const int*);
void g()
{
    const int N = 10;
    [=]
    { 
        int arr[N]; // keine ODR-Verwendung: bezieht sich auf g's const int N
        f(&N); // ODR-Verwendung: verursacht, dass N erfasst wird (durch Kopie)
               // &N ist die Adresse des Member N des Closure-Objekts, nicht g's N
    }();
}

Wenn ein Lambda eine Referenz, die durch Referenz erfasst wurde, odr-verwendet, verwendet es das Objekt, auf das die ursprüngliche Referenz verweist, nicht die erfasste Referenz selbst:

#include <iostream>
auto make_function(int& x)
{
    return [&] { std::cout << x << '\n'; };
}
int main()
{
    int i = 3;
    auto f = make_function(i); // die Verwendung von x in f bindet direkt an i
    i = 5;
    f(); // OK: gibt 5 aus
}

Im Rumpf eines Lambdas mit Standard-Capture = ist der Typ jeder erfassbaren Entität so, als wäre sie erfasst worden (und somit wird oft const-Qualifizierung hinzugefügt, wenn das Lambda nicht mutable ist), selbst wenn die Entität in einem nicht ausgewerteten Operanden steht und nicht erfasst wird (z.B. in decltype ):

void f3()
{
    float x, &r = x;
    [=]
    { // x und r werden nicht erfasst (Auftreten in einem decltype-Operand ist keine ODR-Nutzung)
        decltype(x) y1;        // y1 hat den Typ float
        decltype((x)) y2 = y1; // y2 hat den Typ float const& weil dieser Lambda
                               // nicht mutable ist und x ein L-Wert ist
        decltype(r) r1 = y1;   // r1 hat den Typ float& (Transformation nicht berücksichtigt)
        decltype((r)) r2 = y2; // r2 hat den Typ float const&
    };
}

Jede Entität, die von einem Lambda erfasst wird (implizit oder explizit), wird durch den Lambda-Ausdruck odr-used (daher löst die implizite Erfassung durch ein verschachteltes Lambda die implizite Erfassung im umschließenden Lambda aus).

Alle implizit erfassten Variablen müssen innerhalb des reaching scope der Lambda-Funktion deklariert werden.

Wenn ein Lambda das umschließende Objekt erfasst (als this oder * this ), muss entweder die nächstgelegene umschließende Funktion eine nicht-statische Memberfunktion sein oder das Lambda muss sich in einem Default-Member-Initialisierer befinden:

struct s2
{
    double ohseven = .007;
    auto f() // nächstgelegene umschließende Funktion für die folgenden zwei Lambdas
    {
        return [this]      // erfasse das umschließende s2 per Referenz
        {
            return [*this] // erfasse das umschließende s2 per Kopie (C++17)
            {
                return ohseven;// OK
            }
        }();
    }
    auto g()
    {
        return [] // erfasse nichts
        { 
            return [*this] {};// Fehler: *this nicht durch äußeren Lambda-Ausdruck erfasst
        }();
    }
};

Wenn ein Lambda-Ausdruck (oder eine Spezialisierung des Funktionsaufrufoperators eines generischen Lambda) (since C++14) ODR-verwendet * this oder eine Variable mit automatischer Speicherdauer, muss sie durch den Lambda-Ausdruck erfasst werden.

void f1(int i)
{
    int const N = 20;
    auto m1 = [=]
    {
        int const M = 30;
        auto m2 = [i]
        {
            int x[N][M]; // N und M werden nicht odr-verwendet
                         // (okay, dass sie nicht erfasst wurden)
            x[0][0] = i; // i wird explizit von m2 erfasst
                         // und implizit von m1 erfasst
        };
    };
    struct s1 // lokale Klasse innerhalb von f1()
    {
        int f;
        void work(int n) // nicht-statische Member-Funktion
        {
            int m = n * n;
            int j = 40;
            auto m3 = [this, m]
            {
                auto m4 = [&, j] // Fehler: j wird nicht von m3 erfasst
                {
                    int x = n; // Fehler: n wird implizit von m4 erfasst
                               // aber nicht von m3 erfasst
                    x += m;    // OK: m wird implizit von m4 erfasst
                               // und explizit von m3 erfasst
                    x += i;    // Fehler: i liegt außerhalb des erreichbaren Bereichs
                               // (der bei work() endet)
                    x += f;    // OK: this wird implizit von m4 erfasst
                               // und explizit von m3 erfasst
                };
            };
        }
    };
}

Klassenmitglieder können nicht explizit durch einen Capture ohne Initialisierer erfasst werden (wie oben erwähnt, sind nur Variablen in der capture-list erlaubt):

class S
{
    int x = 0;
    void f()
    {
        int i = 0;
    //  auto l1 = [i, x] { use(i, x); };      // Fehler: x ist keine Variable
        auto l2 = [i, x = x] { use(i, x); };  // OK, Kopiererfassung
        i = 1; x = 1; l2(); // ruft use(0,0) auf
        auto l3 = [i, &x = x] { use(i, x); }; // OK, Referenzerfassung
        i = 2; x = 2; l3(); // ruft use(1,2) auf
    }
};

Wenn ein Lambda ein Mitglied durch implizite Kopiererfassung erfasst, wird keine Kopie dieser Mitgliedsvariable erstellt: Die Verwendung einer Mitgliedsvariable m wird als Ausdruck ( * this ) . m behandelt, und * this wird immer implizit per Referenz erfasst:

class S
{
    int x = 0;
    void f()
    {
        int i = 0;
        auto l1 = [=] { use(i, x); }; // erfasst eine Kopie von i und
                                      // eine Kopie des this-Zeigers
        i = 1; x = 1; l1();           // ruft use(0, 1) auf, als ob
                                      // i per Kopie und x per Referenz
        auto l2 = [i, this] { use(i, x); }; // wie oben, explizit gemacht
        i = 2; x = 2; l2();           // ruft use(1, 2) auf, als ob
                                      // i per Kopie und x per Referenz
        auto l3 = [&] { use(i, x); }; // erfasst i per Referenz und
                                      // eine Kopie des this-Zeigers
        i = 3; x = 2; l3();           // ruft use(3, 2) auf, als ob
                                      // i und x beide per Referenz
        auto l4 = [i, *this] { use(i, x); }; // erstellt eine Kopie von *this,
                                             // einschließlich einer Kopie von x
        i = 4; x = 4; l4();           // ruft use(3, 2) auf, als ob
                                      // i und x beide per Kopie
    }
};

Wenn ein Lambda-Ausdruck in einem Standardargument erscheint, kann er nichts explizit oder implizit erfassen , es sei denn, alle Erfassungen haben Initialisierer, die den Einschränkungen eines Ausdrucks entsprechen, der in einem Standardargument erscheint (seit C++14) :

void f2()
{
    int i = 1;
    void g1( int = [i] { return i; }() ); // Fehler: erfasst etwas
    void g2( int = [i] { return 0; }() ); // Fehler: erfasst etwas
    void g3( int = [=] { return i; }() ); // Fehler: erfasst etwas
    void g4( int = [=] { return 0; }() );       // OK: erfasst nichts
    void g5( int = [] { return sizeof i; }() ); // OK: erfasst nichts
    // C++14
    void g6( int = [x = 1] { return x; }() ); // OK: 1 kann in einem
                                              //     Standardargument erscheinen
    void g7( int = [x = i] { return x; }() ); // Fehler: i kann nicht in einem
                                              //        Standardargument erscheinen
}

Mitglieder von anonymen Unions können nicht erfasst werden. Bit-Felder können nur durch Kopie erfasst werden.

Wenn eine verschachtelte Lambda-Funktion m2 etwas erfasst, das auch von der unmittelbar umschließenden Lambda-Funktion m1 erfasst wird, dann wird die Erfassung von m2 wie folgt transformiert:

  • wenn die umschließende Lambda-Funktion m1 durch Kopie erfasst, m2 erfasst das nicht-statische Mitglied des Closure-Typs von m1 , nicht die ursprüngliche Variable oder * this ; wenn m1 nicht mutable ist, wird das nicht-statische Datenelement als const-qualifiziert betrachtet.
  • wenn die umschließende Lambda-Funktion m1 durch Referenz erfasst, m2 erfasst die ursprüngliche Variable oder * this .
#include <iostream>
int main()
{
    int a = 1, b = 1, c = 1;
    auto m1 = [a, &b, &c]() mutable
    {
        auto m2 = [a, b, &c]() mutable
        {
            std::cout << a << b << c << '\n';
            a = 4; b = 4; c = 4;
        };
        a = 3; b = 3; c = 3;
        m2();
    };
    a = 2; b = 2; c = 2;
    m1();                             // ruft m2() auf und gibt 123 aus
    std::cout << a << b << c << '\n'; // gibt 234 aus
}

Wenn ein Lambda etwas erfasst, kann der Typ des expliziten Objektparameters (falls vorhanden) des Funktionsaufrufoperators nur sein:

  • der Closure-Typ,
  • ein Klassentyp, der öffentlich und eindeutig vom Closure-Typ abgeleitet ist, oder
  • eine Referenz auf einen möglicherweise cv-qualifizierten solchen Typ.
struct C 
{
    template<typename T>
    C(T);
};
void func(int i) 
{
    int x = [=](this auto&&) { return i; }();  // OK
    int y = [=](this C) { return i; }();       // error
    int z = [](this C) { return 42; }();       // OK
    auto lambda = [n = 42] (this auto self) { return n; };
    using Closure = decltype(lambda);
    struct D : private Closure {
        D(Closure l) : Closure(l) {}
        using Closure::operator();
        friend Closure;
    };
    D{lambda}(); // error
}
(seit C++23)

Hinweise

Feature-Test-Makro Wert Std Feature
__cpp_lambdas 200907L (C++11) Lambda-Ausdrücke
__cpp_generic_lambdas 201304L (C++14) Generische Lambda-Ausdrücke
201707L (C++20) Explizite Template-Parameterliste für generische Lambdas
__cpp_init_captures 201304L (C++14) Lambda-Init-Capture
201803L (C++20) Erlaubt Pack-Expansion in Lambda-Init-Capture
__cpp_capture_star_this 201603L (C++17) Lambda-Capture von * this by value als [ = , * this ]
__cpp_constexpr 201603L (C++17) constexpr-Lambda
__cpp_static_call_operator 202207L (C++23) static operator ( ) für capture-less Lambdas

Die Regel für implizite Lambda-Erfassung wurde durch den Defektbericht P0588R1 leicht geändert. Stand 2023-10 haben einige wichtige Implementierungen diesen DR noch nicht vollständig umgesetzt, weshalb in einigen Fällen weiterhin die alte Regel, die ODR-Verwendung erkennt, angewendet wird.

Alte Regel vor P0588R1

Wenn captures einen capture-default hat und das umschließende Objekt (als this oder *this ) oder eine automatische Variable, die odr-verwendbar im Lambda-Rumpf ist , oder eine structured binding , deren entsprechende Variable atomaren Speicher besitzt (seit C++20) , nicht explizit erfasst, erfasst es die Entität implizit , wenn die Entität

  • in einem potenziell ausgewerteten Ausdruck innerhalb eines Ausdrucks genannt wird, der von einem Template-Parameter eines generischen Lambdas abhängt, oder
(seit C++14)

Beispiel

Dieses Beispiel zeigt, wie ein Lambda an einen generischen Algorithmus übergeben wird und wie Objekte, die aus einem Lambda-Ausdruck resultieren, in std::function Objekten gespeichert werden können.

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>
int main()
{
    std::vector<int> c{1, 2, 3, 4, 5, 6, 7};
    int x = 5;
    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end());
    std::cout << "c: ";
    std::for_each(c.begin(), c.end(), [](int i) { std::cout << i << ' '; });
    std::cout << '\n';
    // der Typ eines Closures kann nicht benannt werden, kann jedoch mit auto abgeleitet werden
    // seit C++14 kann Lambda eigene Standardargumente besitzen
    auto func1 = [](int i = 6) { return i + 4; };
    std::cout << "func1: " << func1() << '\n';
    // wie alle aufrufbaren Objekte können Closures in std::function erfasst werden
    // (dies könnte unnötigen Overhead verursachen)
    std::function<int(int)> func2 = [](int i) { return i + 4; };
    std::cout << "func2: " << func2(6) << '\n';
    constexpr int fib_max {8};
    std::cout << "Emuliere `rekursive Lambda`-Aufrufe:\nFibonacci-Zahlen: ";
    auto nth_fibonacci = [](int n)
    {
        std::function<int(int, int, int)> fib = [&](int n, int a, int b)
        {
            return n ? fib(n - 1, a + b, a) : b;
        };
        return fib(n, 0, 1);
    };
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci(i) << (i < fib_max ? ", " : "\n");
    std::cout << "Alternativer Ansatz für Lambda-Rekursion:\nFibonacci-Zahlen: ";
    auto nth_fibonacci2 = [](auto self, int n, int a = 0, int b = 1) -> int
    {
        return n ? self(self, n - 1, a + b, a) : b;
    };
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci2(nth_fibonacci2, i) << (i < fib_max ? ", " : "\n");
#ifdef __cpp_explicit_this_parameter
    std::cout << "C++23 Ansatz zur Lambda-Rekursion:\n";
    auto nth_fibonacci3 = [](this auto self, int n, int a = 0, int b = 1) -> int
    {
         return n ? self(n - 1, a + b, a) : b;
    };
    for (int i{1}; i <= fib_max; ++i)
        std::cout << nth_fibonacci3(i) << (i < fib_max ? ", " : "\n");
#endif
}

Mögliche Ausgabe:

c: 5 6 7
func1: 10
func2: 10
Emuliere `rekursive Lambda`-Aufrufe:
Fibonacci-Zahlen: 0, 1, 1, 2, 3, 5, 8, 13
Alternativer Ansatz für Lambda-Rekursion:
Fibonacci-Zahlen: 0, 1, 1, 2, 3, 5, 8, 13

Fehlerberichte

Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR Angewendet auf Verhalten wie veröffentlicht Korrigiertes Verhalten
CWG 974 C++11 Standardargumente waren in der Parameterliste
eines Lambda-Ausdrucks nicht erlaubt
erlaubt
CWG 1048
( N3638 )
C++11 der Rückgabetyp konnte nur für Lambda-Körper
mit nur einer return -Anweisung abgeleitet werden
verbesserte Rückgabetyp-
ableitung
CWG 1249 C++11 unklar, ob das erfasste Mitglied des umschließenden
nicht-mutierbaren Lambdas als const betrachtet wird
wird als const betrachtet
CWG 1557 C++11 die Sprachbindung des zurückgegebenen Funktionstyps der
Konvertierungsfunktion des Closure-Typs war nicht spezifiziert
hat C++-Sprachbindung
CWG 1607 C++11 Lambda-Ausdrücke konnten in Funktions- und
Funktions-Template-Signaturen erscheinen
nicht erlaubt
CWG 1612 C++11 Mitglieder anonymer Unions konnten erfasst werden nicht erlaubt
CWG 1722 C++11 die Konvertierungsfunktion für erfassungslose Lambdas
hatte eine nicht spezifizierte Ausnahmespezifikation
Konvertierungsfunktion
ist noexcept
CWG 1772 C++11 die Semantik von __func__ im Lambda-Körper war unklar bezieht sich auf den operator()
der Closure-Klasse
CWG 1780 C++14 unklar, ob die Mitglieder der Closure-Typen generischer
Lambdas explizit instanziiert oder spezialisiert werden können
beides nicht erlaubt
CWG 1891 C++11 Closure hatte einen gelöschten Standardkonstruktor
und implizite Kopier-/Verschiebekonstruktoren
kein Standard- und standardmäßige
Kopier-/Verschiebekonstruktoren
CWG 1937 C++11 bezüglich des Effekts des Aufrufs des Ergebnisses der
Konvertierungsfunktion war nicht spezifiziert, auf welchem
Objekt der Aufruf seines operator ( ) denselben Effekt hat
auf einer standardkonstruierten
Instanz des Closure-Typs
CWG 1973 C++11 die Parameterliste des operator ( ) des Closure-Typs
konnte sich auf die in trailing gegebene Parameterliste beziehen
kann sich nur
auf params beziehen
CWG 2011 C++11 für eine per Referenz erfasste Referenz war nicht spezifiziert,
auf welche Entität der Bezeichner der Erfassung verweist
verweist auf die ursprünglich
referenzierte Entität
CWG 2095 C++11 das Verhalten beim Erfassen von Rvalue-Referenzen
auf Funktionen durch Kopie war unklar
geklärt
CWG 2211 C++11 das Verhalten war nicht spezifiziert, wenn eine Erfassung
denselben Namen wie ein Parameter hat
das Programm ist in diesem
Fall fehlerhaft
CWG 2358 C++14 Lambda-Ausdrücke in Standardargumenten mussten
erfassungslos sein, selbst wenn alle Erfassungen mit
Ausdrücken initialisiert werden, die in Standardargumenten erscheinen können
solche Lambda-Ausdrücke
mit Erfassungen erlauben
CWG 2509 C++17 jeder Spezifizierer konnte mehrere Vorkommen
in der Spezifizierer-Sequenz haben
jeder Spezifizierer kann nur
höchstens einmal in der
Spezifizierer-Sequenz erscheinen
CWG 2561 C++23 ein Lambda mit explizitem Objektparameter könnte eine
Konvertierungsfunktion zu einem unerwünschten Funktionszeigertyp haben
hat keine solche
Konvertierungsfunktion
CWG 2881 C++23 operator ( ) mit explizitem Parameter könnte für eine abgeleitete
Klasse instanziiert werden, wenn die Vererbung nicht öffentlich oder mehrdeutig war
als fehlerhaft definiert
P0588R1 C++11 die Regel für implizite Lambda-Erfassung erkannte ODR-Nutzung die Erkennung wurde vereinfacht

Siehe auch

auto Spezifizierer (C++11) spezifiziert einen von einem Ausdruck abgeleiteten Typ
(C++11)
kopierfähiger Wrapper für jedes kopierkonstruierbare aufrufbare Objekt
(Klassentemplate)
nur-verschiebbarer Wrapper für jedes aufrufbare Objekt, das Qualifizierer in einer gegebenen Aufrufsignatur unterstützt
(Klassentemplate)

Externe Links

Verschachtelte Funktion - eine Funktion, die innerhalb einer anderen ( umschließenden ) Funktion definiert ist.