Lambda-Ausdruckssyntax
Dieser Artikel beschreibt Syntax und Strukturelemente von Lambda-Ausdrücken. Eine Beschreibung von Lambda-Ausdrücken finden Sie unter Lambda-Ausdrücke in C++.
Grammatik des Lambda-Ausdrucks
Die folgende formelle Definition aus dem ISO C++11-Standard zeigt die Grammatik eines Lambda-Ausdrucks. (Elemente mit tiefgestelltem opt sind optional.)
lambda-introducer lambda-declaratoropt compound-statement
Diese Syntaxkomponenten werden folgendermaßen angegeben:
lambda-introducer:
[ lambda-captureopt ]
lambda-capture:
capture-default
capture-list
capture-default , capture-list
capture-default:
&
=
capture-list:
capture ...opt
capture-list , capture ...opt
capture:
identifier
& identifier
this
lambda-declarator:
( parameter-declaration-clause ) mutableopt
exception-specificationopt attribute-specifier-seqopt trailing-return-typeopt
Visual Studio unterstützt die standardmäßige C++11-Lambda-Ausdruckssyntax und -Funktionalität mit den folgenden Ausnahmen:
Wie alle anderen Klassen rufen Lambdas nicht automatisch generierte Bewegungskonstruktoren und Bewegungszuweisungsoperatoren ab. Weitere Informationen über Unterstützung für rvalue-Verweisverhalten finden Sie im Abschnitt "Rvalue-Referenzen" unter Unterstützung für C++11-Funktionen (Modern C++).
Das optionale attribute-specifier-seq wird in dieser Version nicht unterstützt.
Visual Studio enthält die folgenden Features zusätzlich zur standardmäßigen C++11-Lambda-Funktionalität:
Statusfreie Lambdas sind unbegrenzt konvertierbar zu Funktionszeigern, die willkürliche Aufrufkonventionen verwenden.
Automatisch hergeleitete Rückgabetypen für Lambda-Texte, die komplizierter als { return expression; } sind, sofern alle Return-Anweisungen denselben Typ haben. (Diese Funktion ist Teil des vorgeschlagenen C++14-Standards.)
Eigenschaften von Lambda-Ausdrücken
In der folgenden Abbildung wird die Grammatik zu einem Beispiel zugeordnet:
lambda-introducer (auch bekannt als Erfassungsklausel)
lambda declarator (auch bekannt als Parameterliste)
mutable (auch bekannt als änderbare Spezifikation)
exception-specification (auch bekannt als Ausnahmespezifikation)
trailing-return-type (auch bekannt als Rückgabetyp)
compound-statement (auch bekannt als Lambda-Text)
Erfassungsklausel
Ein Lambda-Ausdruck ist im Wesentlichen eine Klasse, ein Konstruktor und ein Funktionsaufrufoperator. Genauso wie bei der Definition einer Klasse müssen Sie entscheiden, ob das sich daraus ergebende Objekt Variablen nach Wert, nach Verweis oder überhaupt nicht erfassen soll. Wenn ein Lambda-Ausdruck Zugriff auf lokale Variablen und Funktionsparameter hat, müssen diese erfasst werden. Die Erfassungsklausel (lambda-introducer in der Standardsyntax) gibt an, ob der Lambda-Text auf die Variablen im einschließenden Bereich nach Wert oder nach Verweis zugreifen kann. Auf Variablen mit dem kaufmännischen Und-Zeichen (&) als Präfix erfolgt der Zugriff nach Verweis, und auf Variablen ohne dieses Präfix wird nach Wert zugegriffen.
Eine leere Erfassungsklausel ([ ]) gibt an, dass der Lambda-Text auf keine Variablen im einschließenden Bereich zugreift.
Sie können den Standard-Erfassungsmodus (capture-default in der Standardsyntax) verwenden, um nicht angegebene Variablen entweder nach Wert oder nach Verweis zu erfassen. Sie können den Standarderfassungsmodus angeben, indem Sie & oder = als erstes Element der Erfassungsklausel angeben. Das &-Element gibt an, dass der Lambda-Text auf nicht angegebene Variablen nach Verweis zugreift. Das =-Element gibt an, dass der Lambda-Text auf nicht angegebene Variablen nach Wert zugreift. Wenn beispielsweise der Lambda-Text auf die externe Variable total nach Wert zugreift und auf die externe Variable factor nach Wert, dann sind die folgenden Erfassungsklauseln gleichwertig:
[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]
Es gibt eine verbreitete falsche Vorstellung hinsichtlich capture-default, nach der alle Variablen in einem Bereich unabhängig davon erfasst werden, ob sie im Lambda verwendet werden oder nicht. Dies ist nicht der Fall. Nur die im Lambda erwähnten Variablen werden bei Verwendung eines capture-default erfasst.
Wenn Ihre Erfassungsklausel ein capture-default & umfasst, kann kein identifier in einer capture dieser Erfassungsklausel die Form & identifier haben. Wenn Ihre Erfassungsklausel ein capture-default = umfasst, kann analog dazu kein capture in dieser Erfassungsklausel die Form = identifier haben. Ein Bezeichner oder this kann nicht mehr als einmal in einer Erfassungsklausel angezeigt werden. Der folgende Codeausschnitt veranschaulicht einige Beispiele.
struct S { void f(int i); };
void S::f(int i) {
[&, i]{}; // OK
[&, &i]{}; // ERROR: i preceded by & when & is the default
[=, this]{}; // ERROR: this when = is the default
[i, i]{}; // ERROR: i repeated
}
Eine capture, die von einem Auslassungszeichen gefolgt wird, ist eine Paketerweiterung, wie in diesem Beispiel für eine variadic-Vorlage gezeigt wird:
template<class... Args>
void f(Args... args) {
auto x = [args...] { return g(args...); };
x();
}
Sie können Lambda-Ausdrücke im Text einer Klassenmethode verwenden. Übergeben Sie den this Zeiger auf die Erfassungsklausel, um den Zugriff auf die Methoden und Datenmember der einschließenden Klasse bereitzustellen. Ein Beispiel für die Verwendung eines Lambda-Ausdrucks mit Klassenmethoden finden Sie unter "Beispiel: Verwenden eines Lambda-Ausdrucks in einer Methode" in Beispiele für Lambda-Ausdrücke.
Wenn Sie die Erfassungsklausel verwenden, sollten Sie diese wichtigen Punkte beachten, insbesondere wenn Sie Lambdas mit Multithreading verwenden:
Verweiserfassungen können im Gegensatz zu Werterfassungen dazu verwendet werden, um Variablen outside zu ändern. (mutable ermöglicht Änderungen an Kopien, jedoch nicht am Original.)
Verweiserfassungen können im Gegensatz zu Werterfassungen Änderungen von Variablen outside wiederspiegeln.
Verweiserfassungen führen eine Lebenszeitabhängigkeit ein, während Werterfassungen keine Lebenszeitabhängigkeiten haben.
Parameterliste
Eine Parameterliste (lambda declarator) ist optional und ähnelt der Parameterliste für eine Funktion.
Ein Lambda-Ausdruck kann einen anderen Lambda-Ausdruck als Argument übernehmen. Weitere Informationen finden Sie im Thema Beispiele für Lambda-Ausdrücke unter "Lambda-Ausdrücke höherer Ordnung".
Da die Parameterliste optional ist, können Sie die leeren Klammern weglassen, wenn Sie keine Argumente an den Lambda-Ausdruck übergeben und deren lambda-declarator: keine exception-specification, trailing-return-type oder mutable enthält.
Änderbare Spezifikation
Normalerweise ist ein Aufrufoperator einer Lambda-Funktion "const-by-value", aber das Schlüsselwort mutable hebt dies auf. Er erstellt keine änderbaren Datenmember. Die änderbare Spezifikation aktiviert den Text eines Lambda-Ausdrucks, um Variablen zu ändern, die als Wert erfasst werden. Einige der späteren Beispiele in diesem Artikel veranschaulichen die Verwendung von mutable.
Ausnahmespezifikation
Sie können die throw()-Ausnahmespezifikation verwenden, um anzugeben, dass der Lambda-Ausdruck keine Ausnahmen auslöst. Wie bei normalen Funktionen generiert der Visual C++-Compiler die Warnung C4297, wenn ein Lambda-Ausdruck die throw()-Ausnahmespezifikation deklariert und der Lambda-Text eine Ausnahme auslöst, wie hier gezeigt:
// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
[]() throw() { throw 5; }();
}
Weitere Informationen finden Sie unter Ausnahmespezifikationen.
Rückgabetyp
Der Rückgabetyp von Lambda-Ausdrücken wird automatisch hergeleitet. Sie müssen das Schlüsselwort auto nur dann angeben, wenn Sie trailing-return-type verwenden. trailing-return-type ähnelt dem Rückgabetyp-Ausschnitt einer normalen Methode oder Funktion. Der Rückgabetyp folgt jedoch auf die Parameterliste, und das Schlüsselwort trailing-return-type -> muss vor dem Rückgabetyp angegeben werden.
Sie können den Rückgabetyp eines Lambda-Ausdrucks weglassen, wenn der Lambda-Text nur eine einzelne Return-Anweisung enthält oder der Lambda-Ausdruck keinen Wert zurückgibt. Wenn der Lambda-Text aus einer einzelnen Return-Anweisung besteht, leitet der Compiler den Rückgabetyp vom Typ des Return-Ausdrucks ab. Andernfalls geht der Compiler davon aus, dass der Rückgabetyp void ist. Betrachten Sie die folgenden Beispiele von Codeausschnitten, die dieses Prinzip veranschaulichen.
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
// return type from braced-init-list is not valid
Ein Lambda-Ausdruck kann einen anderen Lambda-Ausdruck als Rückgabewert erzeugen. Weitere Informationen finden Sie im Thema Beispiele für Lambda-Ausdrücke unter "Lambda-Ausdrücke höherer Ordnung".
Lambda-Text
Der Lambda-Textteil (compound-statement in der Standardsyntax) eines Lambda-Ausdrucks kann alles enthalten, was der Text einer gewöhnlichen Methode oder Funktion enthalten kann. Der Text einer gewöhnlichen Funktion und eines Lambda-Ausdrucks kann auf die folgenden Variablenarten zugreifen:
Parameter
Lokal deklarierte Variablen
Klassendatenmember, wenn diese innerhalb einer Klasse deklariert sind und this erfasst wird
Jede beliebige Variable mit statischer Speicherdauer (z. B. globale Variablen)
Außerdem kann ein Lambda-Ausdruck auf Variablen zugreifen, die er vom einschließenden Bereich aufzeichnet. Eine Variable wird explizit aufgezeichnet, wenn sie in der Erfassungsklausel des Lambda-Ausdrucks angezeigt wird. Andernfalls wird die Variable implizit aufgezeichnet. Der Text des Lambda-Ausdrucks verwendet den Standarderfassungsmodus, um auf Variablen zuzugreifen, die implizit erfasst werden.
Das folgende Beispiel enthält einen Lambda-Ausdruck, welcher explizit die Variable n nach ihrem Wert erfasst und implizit die Variable m als Verweis erfasst:
// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);
cout << m << endl << n << endl;
}
Ausgabe:
Da die Variable n als Wert erfasst wird, bleibt der Wert 0 nach dem Aufruf des Lambda-Ausdrucks erhalten. Die mutable-Spezifikation ermöglicht es, dass n innerhalb des Lambda geändert wird.
Obwohl ein Lambda-Ausdruck nur Variablen mit automatischer Speicherdauer aufzeichnen kann, können Sie Variablen verwenden, die eine statische Speicherdauer im Text eines Lambda-Ausdrucks aufweisen. Im folgenden Beispiel wird die Funktion generate und ein Lambda-Ausdruck verwendet, um jedem Element in einem vector-Objekt einen Wert zuzuweisen. Der Lambda-Ausdruck ändert die statische Variable, um den Wert des nächsten Elements zu generieren.
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this is not thread-safe and is shown for illustration only
}
Weitere Informationen finden Sie unter Generieren.
Im folgenden Codebeispiel wird die Funktion aus dem vorherigen Beispiel verwendet, und es wird ein Beispiel eines Lambda-Ausdrucks mit dem STL-Algorithmus generate_n hinzugefügt. Dieser lambda-Ausdruck weist ein Element eines vector-Objekts der Summe der vorangehenden zwei Elemente zu. Das mutable-Schlüsselwort wird verwendet, sodass der Text des Lambda-Ausdrucks seine Kopien der externen Variablen x und y ändern kann, die vom Lambda-Ausdruck als Wert erfasst werden. Da der Lambda-Ausdruck die originalen Variablen x und y als Wert erfasst, bleiben die Werte 1, nachdem das Lambda ausgeführt wird.
// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <typename C> void print(const string& s, const C& c) {
cout << s;
for (const auto& e : c) {
cout << e << " ";
}
cout << endl;
}
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this is not thread-safe and is shown for illustration only
}
int main()
{
// The number of elements in the vector.
const int elementCount = 9;
// Create a vector object with each element set to 1.
vector<int> v(elementCount, 1);
// These variables hold the previous two elements of the vector.
int x = 1;
int y = 1;
// Sets each element in the vector to the sum of the
// previous two elements.
generate_n(v.begin() + 2,
elementCount - 2,
[=]() mutable throw() -> int { // lambda is the 3rd parameter
// Generate current value.
int n = x + y;
// Update previous two values.
x = y;
y = n;
return n;
});
print("vector v after call to generate_n() with lambda: ", v);
// Print the local variables x and y.
// The values of x and y hold their initial values because
// they are captured by value.
cout << "x: " << x << " y: " << y << endl;
// Fill the vector with a sequence of numbers
fillVector(v);
print("vector v after 1st call to fillVector(): ", v);
// Fill the vector with the next sequence of numbers
fillVector(v);
print("vector v after 2nd call to fillVector(): ", v);
}
Ausgabe:
Weitere Informationen finden Sie unter generate_n.
Microsoft-spezifische Modifizierer
Wenn Sie einen Microsoft-spezifischen Modifizierer wie z. B. __declspec verwenden, können Sie diesen direkt nach parameter-declaration-clause in einen Lambda-Ausdruck einfügen—zum Beispiel:
auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };
Um herauszufinden, ob ein Modifizierer von Lambdas unterstützt wird, lesen Sie den entsprechenden Artikel im Abschnitt Microsoft-spezifische Modifizierer in der Dokumentation.
Siehe auch
Referenz
Beispiele für Lambda-Ausdrücke