Lambda-Ausdrücke (seit C++11)
Erzeugt einen Closure (ein unbenanntes Funktionsobjekt, das Variablen im Gültigkeitsbereich erfassen kann).
Inhalt |
[bearbeiten] Syntax
[bearbeiten] Lambda-Ausdrücke ohne explizite Template-Parameterliste (möglicherweise nicht generisch)
[captures ] front-attr (optional) (params ) specs (optional) except (optional)back-attr (optional) trailing (optional) requires (optional) contract-specs (optional) { body } |
(1) | ||||||||
[captures ] { body } |
(2) | (bis C++23) | |||||||
[captures ] front-attr (optional) trailing (optional) contract-specs (optional) { body } |
(2) | (seit C++23) | |||||||
[captures ] front-attr (optional) exceptback-attr (optional) trailing (optional) contract-specs (optional) { body } |
(3) | (seit C++23) | |||||||
[captures ] front-attr (optional) specs except (optional)back-attr (optional) trailing (optional) contract-specs (optional) { body } |
(4) | (seit C++23) | |||||||
[bearbeiten] Lambda-Ausdrücke mit expliziter Template-Parameterliste (immer generisch) (seit C++20)
[captures ] <tparams > t-requires (optional)front-attr (optional) (params ) specs (optional) except (optional)back-attr (optional) trailing (optional) requires (optional) contract-specs (optional) { body } |
(1) | ||||||||
[captures ] <tparams > t-requires (optional) { body } |
(2) | (bis C++23) | |||||||
[captures ] <tparams > t-requires (optional)front-attr (optional) trailing (optional) contract-specs (optional) { body } |
(2) | (seit C++23) | |||||||
[captures ] <tparams > t-requires (optional) front-attr (optional) exceptback-attr (optional) trailing (optional) contract-specs (optional) { body } |
(3) | (seit C++23) | |||||||
[captures ] <tparams > t-requires (optional) front-attr (optional) specs except (optional)back-attr (optional) trailing (optional) contract-specs (optional) { body } |
(4) | (seit C++23) | |||||||
[bearbeiten] Erklärung
| captures | - | Definiert die Entitäten, die erfasst werden sollen. | ||||||||||||
| tparams | - | Eine nicht-leere, durch Kommas getrennte Liste von Template-Parametern, die den Template-Parametern eines generischen Lambdas Namen geben (siehe ClosureType::operator() unten). | ||||||||||||
| t-requires | - | Fügt Constraints zu tparams hinzu.
| ||||||||||||
| front-attr | - | (seit C++23) Eine Attributspezifizierersequenz gilt für operator() des Closure-Typs (und somit kann das Attribut [[noreturn]] verwendet werden). | ||||||||||||
| params | - | Die Parameterliste von operator() des Closure-Typs.
| ||||||||||||
| specs | - | Eine Liste der folgenden Spezifizierer, wobei jeder Spezifizierer höchstens einmal in jeder Sequenz erlaubt ist.
| ||||||||||||
| except | - | Stellt die dynamische Ausnahmespezifikation oder(bis C++20) die noexcept-Spezifikation für operator() des Closure-Typs bereit. | ||||||||||||
| back-attr | - | Eine Attributspezifizierersequenz gilt für den Typ von operator() des Closure-Typs (und somit kann das Attribut [[noreturn]] nicht verwendet werden). | ||||||||||||
| trailing | - | -> ret, wobei ret den Rückgabetyp angibt. | ||||||||||||
| requires | - | (seit C++20) Fügt Constraints zu operator() des Closure-Typs hinzu. | ||||||||||||
| contract-specs | - | (seit C++26) Eine Liste von Funktionsvertragsspezifizierern für operator() des Closure-Typs. | ||||||||||||
| body | - | Der Funktionsrumpf. |
|
Wenn |
(seit C++14) |
Eine Variable __func__ wird am Anfang von body implizit definiert, mit der Semantik, wie sie hier beschrieben ist.
[bearbeiten] Closure-Typ
Der Lambda-Ausdruck ist ein prvalue-Ausdruck eines eindeutigen, unbenannten, nicht-Union- und nicht-aggregat-Typs, bekannt als Closure-Typ, der (für die Zwecke von ADL) im kleinsten Block-, Klassen- oder Namespace-Gültigkeitsbereich deklariert wird, der den Lambda-Ausdruck enthält.
|
Der Closure-Typ ist nur dann ein struktureller Typ, wenn captures leer ist. |
(seit 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 Rumpf des Lambda-Ausdrucks aus, wenn er aufgerufen wird. Beim Zugriff auf eine Variable greift er auf deren erfasste Kopie zu (für Entitäten, die per Kopie erfasst wurden) oder auf das Originalobjekt (für Entitäten, die per Referenz erfasst wurden).
Die Parameterliste von operator() ist params, wenn sie bereitgestellt wird, andernfalls ist die Parameterliste leer.
Der Rückgabetyp von operator() ist der in trailing angegebene Typ.
Wenn trailing nicht bereitgestellt 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 per Kopie erfassten Objekte sind von diesem operator() aus nicht modifizierbar. Ein expliziter const-Qualifizierer ist nicht erlaubt. operator() ist niemals virtuell und darf keinen volatile-Qualifizierer haben.
|
operator() ist immer constexpr, wenn es die Anforderungen einer constexpr-Funktion erfüllt. Es ist auch constexpr, wenn das Schlüsselwort constexpr in den Lambda-Spezifizierern verwendet wurde. |
(seit C++17) |
|
operator() ist eine Sofortfunktion (immediate function), 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 Objektmemberfunktion, wenn params einen expliziten Objektparameter enthält. |
(seit C++23) |
|
Für jeden Parameter in params, dessen Typ als auto angegeben ist, wird ein erfundener Template-Parameter zu template-params in der Reihenfolge seines Erscheinens hinzugefügt. Der erfundene Template-Parameter kann ein Parameterpaket sein, wenn der entsprechende Funktionsmember von params ein Funktionsparameterpaket ist. |
(seit C++14) |
|
Wenn die Lambda-Definition eine explizite Template-Parameterliste verwendet, wird diese Liste mit operator) verwendet. Für jeden Parameter in params, dessen Typ als auto angegeben ist, wird ein zusätzlicher erfundener Template-Parameter am Ende dieser Template-Parameterliste angehängt. |
(seit C++20) |
Die Ausnahmespezifikation except im Lambda-Ausdruck gilt für operator).
Für die Zwecke der Namenssuche, der Bestimmung des Typs und Werts des this-Zeigers und für den Zugriff auf nicht-statische Klassenmember wird der Rumpf des operator() des Closure-Typs im Kontext des Lambda-Ausdrucks betrachtet.
struct X { int x, y; int operator()(int); void f() { // the context of the following lambda is the member function X::f [=]() -> int { return operator()(this->x + y); // X::operator()(this->x + (*this).y) // this has type X* }; } };
Dangling-Referenzen
Wenn eine Nicht-Referenz-Entität per Referenz erfasst wird, implizit oder explizit, und operator() des Closure-Objekts aufgerufen wird, nachdem die Lebensdauer der Entität beendet ist, tritt undefiniertes Verhalten auf. C++ Closures verlängern nicht die Lebensdauer von per Referenz erfassten Objekten.
Das Gleiche gilt für die Lebensdauer des aktuellen *this-Objekts, das über this erfasst wurde.
- ↑ Obwohl die Rückgabetypermittlung für Funktionen in C++14 eingeführt wurde, ist ihre Regel für die Rückgabetypermittlung von Lambdas in C++11 verfügbar.
ClosureType::operator ret(*)(params)()
| Capture-loses, nicht-generisches 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-loses, generisches 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 Konversionsfunktion ist nur definiert, wenn der Lambda-Ausdruck keine captures hat und keinen expliziten Objektparameter(seit C++23). Sie ist eine öffentliche, constexpr,(seit C++17) nicht-virtuelle, nicht-explizite, const noexcept Memberfunktion des Closure-Objekts.
|
Diese Funktion ist eine Sofortfunktion (immediate function), wenn der Funktionsaufrufoperator (oder die Spezialisierung für generische Lambdas) eine Sofortfunktion ist. |
(seit C++20) |
|
Ein generisches capture-loses Lambda hat eine benutzerdefinierte Konversionsfunktionstemplate mit derselben erfindung von Template-Parametern wie operator(). |
(seit C++14) |
|
Der von der Konversionsfunktion zurückgegebene Wert ist ein Zeiger auf eine Funktion mit C++ Sprachverknüpfung, die beim Aufrufen die gleiche Auswirkung hat wie der Aufruf des Funktionsaufrufoperators des Closure-Typs für eine standardkonstruierte Instanz des Closure-Typs. |
(bis C++14) |
|
Der von der Konversionsfunktion (Template) zurückgegebene Wert ist ein Zeiger auf eine Funktion mit C++ Sprachverknüpfung, die beim Aufrufen die gleiche Auswirkung hat wie
|
(seit C++14) (bis C++23) |
|
Der von der Konversionsfunktion (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 eine noexcept-Funktion. |
(seit C++17) |
ClosureType::ClosureType()
| ClosureType() = default; |
(seit C++20) (nur wenn keine Erfassungen 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äßig definierten Standardkonstruktor. Andernfalls hat er keinen Standardkonstruktor (dies schließt den Fall ein, dass es eine capture-default gibt, auch wenn sie tatsächlich nichts erfasst). |
(seit C++20) |
Der Kopierkonstruktor und der Move-Konstruktor werden als standardmäßig definiert 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 Erfassungen angegeben sind) |
|
| ClosureType& operator=(const ClosureType&) = delete; |
(seit C++20) (andernfalls) |
|
|
Der Kopierzuweisungsoperator wird als gelöscht definiert (und der Move-Zuweisungsoperator wird nicht deklariert). Closure-Typen sind nicht CopyAssignable. |
(bis C++20) |
|
Wenn keine captures angegeben sind, hat der Closure-Typ einen standardmäßig definierten Kopierzuweisungsoperator und einen standardmäßig definierten Move-Zuweisungsoperator. Andernfalls hat er einen gelöschten Kopierzuweisungsoperator (dies schließt den Fall ein, dass es eine capture-default gibt, auch wenn sie tatsächlich nichts erfasst). |
(seit C++20) |
ClosureType::~ClosureType()
| ~ClosureType() = default; |
||
Der Destruktor wird implizit deklariert.
ClosureType::Captures
| T1 a; T2 b; |
||
Wenn der Lambda-Ausdruck etwas per Kopie erfasst (entweder implizit mit der Capture-Klausel [=] oder explizit mit einer Erfassung, die nicht das Zeichen & enthält, z. B. [a, b, c]), enthält der Closure-Typ unbenannte, nicht-statische Datenmember, deklariert in unspezifizierter Reihenfolge, die Kopien aller so erfassten Entitäten halten.
Die Datenmember, die Erfassungen ohne Initialisierer entsprechen, werden direkt initialisiert, wenn der Lambda-Ausdruck ausgewertet wird. Diejenigen, die Erfassungen mit Initialisierern entsprechen, werden gemäß den Anforderungen des Initialisierers initialisiert (können kopier- oder direkt-initialisiert werden). Wenn ein Array erfasst wird, werden Array-Elemente in aufsteigender Indexreihenfolge direkt initialisiert. Die Reihenfolge, in der die Datenmember initialisiert werden, ist die Reihenfolge, in der sie deklariert werden (die unspezifiziert ist).
Der Typ jedes Datenmembers ist der Typ der entsprechenden erfassten Entität, es sei denn, die Entität hat einen Referenztyp (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 die Entitäten, die per Referenz erfasst werden (mit dem capture-default [&] oder bei Verwendung des Zeichens &, z. B. [&a, &b, &c]), ist es unspezifiziert, ob zusätzliche Datenmember im Closure-Typ deklariert werden, aber solche zusätzlichen Member müssen LiteralType erfüllen(seit C++17).
|
Lambda-Ausdrücke sind nicht erlaubt in nicht ausgewerteten Ausdrücken, Template-Argumenten, Alias-Deklarationen, typedef-Deklarationen und überall in einer Funktions- (oder Funktions-Template-) Deklaration außer dem Funktionsrumpf und den Standardargumenten der Funktion. |
(bis C++20) |
[bearbeiten] Lambda-Erfassung
captures definiert die externen Variablen, die aus dem Lambda-Funktionsrumpf zugänglich sind. Seine Syntax ist wie folgt definiert:
| capture-default | (1) | ||||||||
| capture-list | (2) | ||||||||
capture-default , capture-list |
(3) | ||||||||
| capture-default | - | einer von & und = |
| capture-list | - | eine durch Kommas getrennte Liste von capture s |
Die Syntax von capture ist wie folgt definiert:
| identifier | (1) | ||||||||
identifier ... |
(2) | ||||||||
| identifier initializer | (3) | (seit C++14) | |||||||
& identifier |
(4) | ||||||||
& identifier ... |
(5) | ||||||||
& identifier initializer |
(6) | (seit C++14) | |||||||
this
|
(7) | ||||||||
* this |
(8) | (seit C++17) | |||||||
... identifier initializer |
(9) | (seit C++20) | |||||||
& ... identifier initializer |
(10) | (seit C++20) | |||||||
Wenn der capture-default & ist, dürfen nachfolgende einfache Erfassungen nicht mit & beginnen.
struct S2 { void f(int i); }; void S2::f(int i) { [&] {}; // OK: by-reference capture default [&, i] {}; // OK: by-reference capture, except i is captured by copy [&, &i] {}; // Error: by-reference capture when by-reference is the default [&, this] {}; // OK, equivalent to [&] [&, this, i] {}; // OK, equivalent to [&, i] }
Wenn der capture-default = ist, müssen nachfolgende einfache Erfassungen mit & oder *this(seit C++17) oder this(seit C++20) beginnen.
struct S2 { void f(int i); }; void S2::f(int i) { [=] {}; // OK: by-copy capture default [=, &i] {}; // OK: by-copy capture, except i is captured by reference [=, *this] {}; // until C++17: Error: invalid syntax // since C++17: OK: captures the enclosing S2 by copy [=, this] {}; // until C++20: Error: this when = is the default // since C++20: OK, same as [=] }
Jede Erfassung darf nur einmal vorkommen und ihr Name muss sich von jedem Parameternamen unterscheiden.
struct S2 { void f(int i); }; void S2::f(int i) { [i, i] {}; // Error: i repeated [this, *this] {}; // Error: "this" repeated (C++17) [i] (int i) {}; // Error: parameter and capture have the same name }
Ein Lambda-Ausdruck kann eine Variable verwenden, ohne sie zu erfassen, wenn die Variable
- eine nicht-lokale Variable ist oder eine statische oder Thread-lokale Speicherdauer hat (in diesem Fall kann die Variable nicht erfasst werden), oder
- eine Referenz ist, die mit einem Konstanten Ausdruck initialisiert wurde.
Ein Lambda-Ausdruck kann den Wert einer Variablen lesen, ohne sie zu erfassen, wenn die Variable
- einen konstanten, nicht-volatilen ganzzahligen Typ oder einen Aufzählungstyp hat und mit einem Konstanten Ausdruck initialisiert wurde, oder
- constexpr ist und keine veränderlichen Member hat.
Das aktuelle Objekt (*this) kann implizit erfasst werden, wenn ein Capture-Default vorhanden ist. Wenn es implizit erfasst wird, wird es immer per Referenz erfasst, auch wenn der Capture-Default = ist. Die implizite Erfassung von *this, wenn der Capture-Default = ist, ist veraltet.(seit C++20)
Nur Lambda-Ausdrücke, die eine der folgenden Bedingungen erfüllen, dürfen einen capture-default oder eine capture ohne Initialisierer haben.
- Ihr innerster umschließender Geltungsbereich ist ein Block-Geltungsbereich.
- Sie tritt in einem Standard-Member-Initialisierer auf, und ihr innerster umschließender Geltungsbereich ist der entsprechende Klassen-Geltungsbereich.
|
(seit C++26) |
Für einen solchen Lambda-Ausdruck wird der *erreichende Geltungsbereich* als die Menge der umschließenden Geltungsbereiche bis einschließlich der innersten umschließenden Funktion (und ihrer Parameter) definiert. Dies schließt verschachtelte Block-Geltungsbereiche und die Geltungsbereiche von umschließenden Lambdas ein, wenn dieses Lambda verschachtelt ist.
Der identifier in jeder Erfassung ohne Initialisierer (außer der this-Erfassung) wird mittels der üblichen unqualifizierten Namenssuche im *erreichenden Geltungsbereich* des Lambdas nachgeschlagen. Das Ergebnis der Suche muss eine Variable mit automatischer Speicherdauer sein, die im erreichenden Geltungsbereich deklariert wurde, oder eine strukturierte Bindung, deren entsprechende Variable solche Anforderungen erfüllt(seit C++20). Die Entität wird *explizit erfasst*.
|
Eine Erfassung mit einem Initialisierer, genannt *Init-Erfassung*, verhält sich so, als würde sie eine Variable mit dem Typspezifizierer
Dies wird verwendet, um nur-move-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. |
(seit C++14) |
Wenn captures einen capture-default hat und das umschließende Objekt (als this oder *this) nicht explizit erfasst, oder eine automatische Variable, die im Lambda-Körper odr-verwendbar ist, oder eine strukturierte Bindung, deren entsprechende Variable eine atomare Speicherdauer hat(seit C++20), erfasst es die Entität *implizit*, wenn die Entität in einem potenziell ausgewerteten Ausdruck innerhalb eines Ausdrucks (einschließlich des impliziten this-> vor der Verwendung eines nicht-statischen Klassenmembers) genannt wird.
Für die Bestimmung impliziter Erfassungen wird typeid niemals als etwas betrachtet, das seine Operanden nicht ausgewertet macht.
|
Entitäten können implizit erfasst werden, selbst wenn sie nur innerhalb einer verworfenen Anweisung nach der Instanziierung des Lambda-Körpers genannt werden. |
(seit 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: calls #1, does not capture x auto g0 = [](auto a) { f(x); }; // same as above auto l1 = [=]{ f(x); }; // OK: captures x (since P0588R1) and calls #1 // the capture can be optimized away auto g1 = [=](auto a) { f(x); }; // same as above auto ltid = [=]{ typeid(x); }; // OK: captures x (since P0588R1) // even though x is unevaluated // the capture can be optimized away auto g2 = [=](auto a) { int selector[sizeof(a) == 1 ? 1 : 2] = {}; f(x, selector); // OK: is a dependent expression, so captures x }; auto g3 = [=](auto a) { typeid(a + x); // captures x regardless of // whether a + x is an unevaluated operand }; constexpr NoncopyableLiteralType w{42}; auto l4 = []{ return w.n_; }; // OK: w is not odr-used, capture is unnecessary // auto l5 = [=]{ return w.n_; }; // error: w needs to be captured by copy }
Wenn der Körper eines Lambdas eine Entität, die per Kopie erfasst wurde, odr-verwendet, wird auf das Mitglied des Closure-Typs zugegriffen. Wenn die Entität nicht odr-verwendet wird, erfolgt der Zugriff auf das Originalobjekt.
void f(const int*); void g() { const int N = 10; [=] { int arr[N]; // not an odr-use: refers to g's const int N f(&N); // odr-use: causes N to be captured (by copy) // &N is the address of the closure object's member N, not g's N }(); }
Wenn ein Lambda eine Referenz odr-verwendet, die per Referenz erfasst wurde, 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); // the use of x in f binds directly to i i = 5; f(); // OK: prints 5 }
Innerhalb des Körpers eines Lambdas mit Capture-Default = ist der Typ jeder erfassbaren Entität so, als ob sie erfasst würde (und damit wird häufig eine const-Qualifikation hinzugefügt, wenn das Lambda nicht mutable ist), obwohl die Entität in einem nicht ausgewerteten Operand ist und nicht erfasst wird (z. B. in decltype).
void f3() { float x, &r = x; [=] { // x and r are not captured (appearance in a decltype operand is not an odr-use) decltype(x) y1; // y1 has type float decltype((x)) y2 = y1; // y2 has type float const& because this lambda // is not mutable and x is an lvalue decltype(r) r1 = y1; // r1 has type float& (transformation not considered) decltype((r)) r2 = y2; // r2 has type float const& }; }
Jede Entität, die von einem Lambda erfasst wird (implizit oder explizit), wird vom Lambda-Ausdruck odr-verwendet (daher löst die implizite Erfassung durch ein verschachteltes Lambda eine implizite Erfassung im umschließenden Lambda aus).
Alle implizit erfassten Variablen müssen innerhalb des *erreichenden Geltungsbereichs* des Lambdas deklariert werden.
Wenn ein Lambda das umschließende Objekt erfasst (als this oder *this), muss die nächste umschließende Funktion entweder eine nicht-statische Member-Funktion sein oder das Lambda muss sich in einem Standard-Member-Initialisierer befinden.
struct s2 { double ohseven = .007; auto f() // nearest enclosing function for the following two lambdas { return [this] // capture the enclosing s2 by reference { return [*this] // capture the enclosing s2 by copy (C++17) { return ohseven; // OK } }(); } auto g() { return [] // capture nothing { return [*this] {}; // error: *this not captured by outer lambda expression }(); } };
Wenn ein Lambda-Ausdruck (oder eine Spezialisierung des Funktionsaufrufoperators eines generischen Lambdas)(seit C++14) *this oder eine Variable mit automatischer Speicherdauer odr-verwendet, muss sie vom 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 and M are not odr-used // (ok that they are not captured) x[0][0] = i; // i is explicitly captured by m2 // and implicitly captured by m1 }; }; struct s1 // local class within f1() { int f; void work(int n) // non-static member function { int m = n * n; int j = 40; auto m3 = [this, m] { auto m4 = [&, j] // error: j is not captured by m3 { int x = n; // error: n is implicitly captured by m4 // but not captured by m3 x += m; // OK: m is implicitly captured by m4 // and explicitly captured by m3 x += i; // error: i is outside of the reaching scope // (which ends at work()) x += f; // OK: this is captured implicitly by m4 // and explicitly captured by m3 }; }; } }; }
Klassenmember können nicht explizit mit einer Erfassung ohne Initialisierer erfasst werden (wie oben erwähnt, sind nur Variablen in der capture-list zulässig).
class S { int x = 0; void f() { int i = 0; // auto l1 = [i, x] { use(i, x); }; // error: x is not a variable auto l2 = [i, x = x] { use(i, x); }; // OK, copy capture i = 1; x = 1; l2(); // calls use(0,0) auto l3 = [i, &x = x] { use(i, x); }; // OK, reference capture i = 2; x = 2; l3(); // calls use(1,2) } };
Wenn ein Lambda einen Member mithilfe der impliziten Kopiererfassung erfasst, wird dieser Member-Variable nicht kopiert: Die Verwendung einer Member-Variable 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); }; // captures a copy of i and // a copy of the this pointer i = 1; x = 1; l1(); // calls use(0, 1), as if // i by copy and x by reference auto l2 = [i, this] { use(i, x); }; // same as above, made explicit i = 2; x = 2; l2(); // calls use(1, 2), as if // i by copy and x by reference auto l3 = [&] { use(i, x); }; // captures i by reference and // a copy of the this pointer i = 3; x = 2; l3(); // calls use(3, 2), as if // i and x are both by reference auto l4 = [i, *this] { use(i, x); }; // makes a copy of *this, // including a copy of x i = 4; x = 4; l4(); // calls use(3, 2), as if // i and x are both by copy } };
Wenn ein Lambda-Ausdruck in einem Standardargument vorkommt, darf er nichts explizit oder implizit erfassen, es sei denn, alle Erfassungen haben Initialisierer, die die Einschränkungen eines in einem Standardargument vorkommenden Ausdrucks erfüllen(seit C++14).
void f2() { int i = 1; void g1( int = [i] { return i; }() ); // error: captures something void g2( int = [i] { return 0; }() ); // error: captures something void g3( int = [=] { return i; }() ); // error: captures something void g4( int = [=] { return 0; }() ); // OK: capture-less void g5( int = [] { return sizeof i; }() ); // OK: capture-less // C++14 void g6( int = [x = 1] { return x; }() ); // OK: 1 can appear // in a default argument void g7( int = [x = i] { return x; }() ); // error: i cannot appear // in a default argument }
Member von anonymen Unions können nicht erfasst werden. Bitfelder können nur per Kopie erfasst werden.
Wenn ein verschachteltes Lambda m2 etwas erfasst, das auch vom unmittelbar umschließenden Lambda m1 erfasst wird, dann wird die Erfassung von m2 wie folgt transformiert:
- wenn das umschließende Lambda
m1per Kopie erfasst, erfasstm2das nicht-statische Member des Closure-Typs vonm1, nicht die ursprüngliche Variable oder *this; wennm1nicht mutable ist, gilt das nicht-statische Datenmitglied als const-qualifiziert. - wenn das umschließende Lambda
m1per Referenz erfasst, erfasstm2die 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(); // calls m2() and prints 123 std::cout << a << b << c << '\n'; // prints 234 }
|
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) |
[edit] Hinweise
| Feature-Testmakro | 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 Vorlagenparameterliste für generische Lambdas | |
__cpp_init_captures |
201304L |
(C++14) | Lambda-Init-Erfassung |
201803L |
(C++20) | Erlauben Sie Pack-Expansion in der Lambda-Init-Erfassung | |
__cpp_capture_star_this |
201603L |
(C++17) | Lambda-Erfassung von *this per Wert als [=, *this] |
__cpp_constexpr |
201603L |
(C++17) | constexpr Lambda |
__cpp_static_call_operator |
202207L |
(C++23) | statische operator() für erfassungslose Lambdas |
Die Regel für die implizite Lambda-Erfassung wurde durch den Defect Report P0588R1 geringfügig geändert. Zum Zeitpunkt 2023-10 haben einige wichtige Implementierungen den DR noch nicht vollständig implementiert, und daher wird in einigen Fällen die alte Regel, die ODR-Verwendung erkennt, weiterhin verwendet.
| Alte Regel vor P0588R1 | ||
|---|---|---|
|
Wenn captures einen capture-default hat und das umschließende Objekt (als
|
[edit] 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'; // the type of a closure cannot be named, but can be inferred with auto // since C++14, lambda could own default arguments auto func1 = [](int i = 6) { return i + 4; }; std::cout << "func1: " << func1() << '\n'; // like all callable objects, closures can be captured in std::function // (this may incur unnecessary overhead) std::function<int(int)> func2 = [](int i) { return i + 4; }; std::cout << "func2: " << func2(6) << '\n'; constexpr int fib_max {8}; std::cout << "Emulate `recursive lambda` calls:\nFibonacci numbers: "; 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 << "Alternative approach to lambda recursion:\nFibonacci numbers: "; 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 approach to lambda recursion:\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 Emulate `recursive lambda` calls: Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13 Alternative approach to lambda recursion: Fibonacci numbers: 0, 1, 1, 2, 3, 5, 8, 13
[edit] Defect Reports
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 | Standardargument war im Parameterlisten eines Lambda-Ausdrucks nicht erlaubt |
erlaubt |
| CWG 1048 (N3638) |
C++11 | Der Rückgabetyp konnte nur für Lambda Körper, die nur eine einzige return-Anweisung enthalten, abgeleitet werden |
verbesserte die Rückgabe Typableitung |
| CWG 1249 | C++11 | Es war nicht klar, ob das erfasste Member des umschließenden nicht-mutablen Lambdas als const betrachtet wurde oder nicht |
als const betrachtet |
| CWG 1557 | C++11 | Die Sprachverknüpfung des zurückgegebenen Funktionstyps der Konvertierungsfunktion des Closure-Typs war nicht spezifiziert |
hat C++ Sprachverknüpfung |
| CWG 1607 | C++11 | Lambda-Ausdrücke konnten in Funktions- und Funktionstemplatsignaturen vorkommen |
nicht erlaubt |
| CWG 1612 | C++11 | Member von anonymen Unions konnten erfasst werden | nicht erlaubt |
| CWG 1722 | C++11 | Die Konvertierungsfunktion für erfassungslose Lambdas hatte eine nicht spezifizierte Ausnahmeangabe |
Konvertierungsfunktion ist noexcept |
| CWG 1772 | C++11 | Die Semantik von __func__ im Lambda-Körper war nicht klar | es verweist auf den Closure Klassen-Operator() |
| CWG 1780 | C++14 | Es war unklar, ob die Member der Closure-Typen von generischen Lambdas explizit instanziiert oder explizit spezialisiert werden können |
keines von beiden ist erlaubt |
| CWG 1891 | C++11 | Closure hatte einen gelöschten Standardkonstruktor und implizite Kopier-/Move-Konstruktoren |
keine Standard- und keine abgeleiteten Kopier-/Move-Konstruktoren |
| CWG 1937 | C++11 | In Bezug auf die Auswirkung des Aufrufs des Ergebnisses der Konvertierungsfunktion war nicht spezifiziert, auf welchem Objekt der Aufruf von 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 angegebene Parameterliste beziehen |
kann sich nur beziehen auf params |
| CWG 2011 | C++11 | Bei einer per Referenz erfassten Referenz war nicht spezifiziert, auf welche Entität sich der Bezeichner der Erfassung bezieht |
es bezieht sich auf die ursprünglich referenzierte Entität |
| CWG 2095 | C++11 | Das Verhalten beim Erfassen von Rvalue-Referenzen zu Funktionen per Kopie war nicht klar |
wurde klargestellt |
| CWG 2211 | C++11 | Das Verhalten war nicht spezifiziert, wenn eine Erfassung denselben Namen wie ein Parameter hat |
das Programm ist schlecht ge- formt in diesem Fall |
| CWG 2358 | C++14 | Lambda-Ausdrücke, die in Standardargumenten vorkamen, mussten erfassungslose sein, selbst wenn alle Erfassungen mit Ausdrücken initialisiert wurden, die in Standardargumenten vorkommen können |
erlaube solche Lambda Ausdrücke mit Erfassungen |
| CWG 2509 | C++17 | Jeder Spezifizierer konnte mehrere Vorkommen in der Spezifizierersequenz haben |
Jeder Spezifizierer kann nur höchstens einmal in der Spezifizierersequenz vorkommen |
| CWG 2561 | C++23 | Ein Lambda mit explizitem Objektparameter konnte eine Konvertierungsfunktion zu einem unerwünschten Funktionszeigertyp haben |
es hat keine solche Konvertierungsfunktion |
| CWG 2881 | C++23 | operator() mit explizitem Parameter konnte für eine abgeleitete Klasse instanziiert werden, wenn die Vererbung nicht öffentlich oder mehrdeutig war |
machte fehlerhaft |
| P0588R1 | C++11 | Die Regel für die implizite Lambda-Erfassung erkannte ODR-Verwendung | Die Erkennung wird vereinfacht |
[edit] Siehe auch
auto-Spezifizierer (C++11) |
spezifiziert einen Typ, der aus einem Ausdruck abgeleitet wird |
| (C++11) |
kopierfähiger Wrapper für jedes kopierkonstruierbare aufrufbare Objekt (Klassen-Template) |
| (C++23) |
Move-only-Wrapper für jedes aufrufbare Objekt, das Qualifizierer in einer gegebenen Aufrufsignatur unterstützt (Klassen-Template) |
[edit] Externe Links
| Verschachtelte Funktion - eine Funktion, die innerhalb einer anderen (umschließenden) Funktion definiert ist. |