Lambda expressions (since C++11)
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) | |||||||
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.
|
||||||||||||
| 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.
|
||||||||||||
| specs | - |
Eine Liste der folgenden Spezifizierer, jeder Spezifizierer ist in jeder Sequenz höchstens einmal erlaubt.
|
||||||||||||
| 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
|
(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. |
(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: |
(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.
- ↑ 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
>
|
(seit C++14)
(bis C++17) |
|
|
template
<
template
-
params
>
using
fptr_t
=
/* siehe unten */
;
template
<
template
-
params
>
|
(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 ( ) . |
(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:
|
(seit C++14)
(bis C++23) |
|
Der von der Konvertierungsfunktion (-template) zurückgegebene Wert ist
|
(seit C++23) |
|
Diese Funktion ist constexpr, wenn der Funktionsaufrufoperator (oder die Spezialisierung, für generische Lambdas) constexpr ist. 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) | |||||||
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:
- Sein innerster einschließender Gültigkeitsbereich ist ein Blockgültigkeitsbereich .
- Es erscheint innerhalb eines Standard-Memberinitialisierers , und sein innerster einschließender Gültigkeitsbereich ist der entsprechende Klassengültigkeitsbereich .
|
(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
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
m1durch Kopie erfasst,m2erfasst das nicht-statische Mitglied des Closure-Typs vonm1, nicht die ursprüngliche Variable oder * this ; wennm1nicht mutable ist, wird das nicht-statische Datenelement als const-qualifiziert betrachtet. -
wenn die umschließende Lambda-Funktion
m1durch Referenz erfasst,m2erfasst 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:
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
|
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) |
|
(C++23)
|
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. |