[expr.prim.lambda.capture] (original) (raw)
7 Expressions [expr]
7.5 Primary expressions [expr.prim]
7.5.6 Lambda expressions [expr.prim.lambda]
7.5.6.3 Captures [expr.prim.lambda.capture]
The body of a lambda-expression may refer to local entities of enclosing scopes by capturing those entities, as described below.
If a lambda-capture includes a capture-default that is &, no identifier in a simple-capture of thatlambda-capture shall be preceded by &.
If a lambda-capture includes acapture-default that is =, eachsimple-capture of that lambda-capture shall be of the form “& identifier ...”, “this”, or “* this”.
[Note 1:
The form [&,this] is redundant but accepted for compatibility with C++ 2014.
— _end note_]
Ignoring appearances ininitializers of init-captures, an identifier orthis shall not appear more than once in alambda-capture.
[Example 1: struct S2 { void f(int i); };void S2::f(int i) { [&, i]{ }; [&, this, i]{ }; [&, &i]{ }; [=, *this]{ }; [=, this]{ }; [i, i]{ }; [this, *this]{ }; } — _end example_]
The simple-captures this and * thisdenote the local entity *this.
An entity that is designated by asimple-captureis said to be explicitly captured.
[Example 2: void f() { int x = 0;auto g = [x](int x) { return 0; }; auto h = [y = 0]<typename y>(y) { return 0; }; } — _end example_]
An init-capture without ellipsis behaves as if it declares and explicitly captures a variable of the form “auto init-capture ;”, except that:
- if the capture is by copy (see below), the non-static data member declared for the capture and the variable are treated as two different ways of referring to the same object, which has the lifetime of the non-static data member, and no additional copy and destruction is performed, and
- if the capture is by reference, the variable's lifetime ends when the closure object's lifetime ends.
[Note 2:
This enables an init-capture like “x = std::move(x)”; the second “x” must bind to a declaration in the surrounding context.
— _end note_]
[Example 3: int x = 4;auto y = [&r = x, x = x+1]()->int { r += 2;return x+2;}(); auto z = [a = 42](int a) { return 1; }; auto counter = [i=0]() mutable -> decltype(i) { return i++;}; — _end example_]
For the purposes of lambda capture, an expression potentially references local entities as follows:
- An id-expression that names a local entity potentially references that entity; an id-expression that names one or more non-static class members and does not form a pointer to member ([expr.unary.op]) potentially references *this.
[Note 3:
This occurs even if overload resolution selects a static member function for the id-expression.
— _end note_] - A this expression potentially references *this.
If an expression potentially references a local entity within a scope in which it is odr-usable ([basic.def.odr]), and the expression would be potentially evaluated if the effect of any enclosing typeid expressions ([expr.typeid]) were ignored, the entity is said to be implicitly capturedby each intervening lambda-expression with an associatedcapture-default that does not explicitly capture it.
The implicit capture of *this is deprecated when thecapture-default is =; see [depr.capture.this].
[Example 4: void f(int, const int (&)[2] = {}); void f(const int&, const int (&)[1]); void test() { const int x = 17;auto g = [](auto a) { f(x); };auto g1 = [=](auto a) { f(x); };auto g2 = [=](auto a) { int selector[sizeof(a) == 1 ? 1 : 2]{}; f(x, selector); };auto g3 = [=](auto a) { typeid(a + x); };}
Within g1, an implementation can optimize away the capture of x as it is not odr-used.
— _end example_]
[Note 4:
The set of captured entities is determined syntactically, and entities are implicitly captured even if the expression denoting a local entity is within a discarded statement ([stmt.if]).
[Example 5: template<bool B> void f(int n) { [=](auto a) { if constexpr (B && sizeof(a) > 4) { (void)n; } }(0);} — _end example_]
— _end note_]
An entity is captured if it is captured explicitly or implicitly.
[Example 6: void f1(int i) { int const N = 20;auto m1 = [=]{ int const M = 30;auto m2 = [i]{ int x[N][M]; x[0][0] = i; };};struct s1 { int f;void work(int n) { int m = n*n;int j = 40;auto m3 = [this,m] { auto m4 = [&,j] { int x = n; x += m; x += i; x += f; };};} };} struct s2 { double ohseven = .007;auto f() { return [this] { return [*this] { return ohseven; };}();} auto g() { return [] { return [*this] { }; }();} }; — _end example_]
[Note 6:
Because local entities are not odr-usable within a default argument ([basic.def.odr]), a lambda-expression appearing in a default argument cannot implicitly or explicitly capture any local entity.
— _end note_]
[Example 7: void f2() { int i = 1;void g1(int = ([i]{ return i; })()); void g2(int = ([i]{ return 0; })()); void g3(int = ([=]{ return i; })()); void g4(int = ([=]{ return 0; })()); void g5(int = ([]{ return sizeof i; })()); void g6(int = ([x=1]{ return x; })()); void g7(int = ([x=i]{ return x; })()); } — _end example_]
An entity is captured by copy if
- it is implicitly captured, the capture-default is =, and the captured entity is not *this, or
- it is explicitly captured with a capture that is not of the formthis,& identifier ..., or& ... identifier initializer.
For each entity captured by copy, an unnamed non-static data member is declared in the closure type.
The declaration order of these members is unspecified.
The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entity otherwise.
A member of an anonymous union shall not be captured by copy.
[Note 7:
An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type.
However, such an id-expression can still cause the implicit capture of the entity.
— _end note_]
If *this is captured by copy, each expression that odr-uses *this is transformed to instead refer to the corresponding unnamed data member of the closure type.
[Example 8: void f(const int*);void g() { const int N = 10;[=] { int arr[N]; f(&N); };} — _end example_]
An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy.
It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference.
If declared, such non-static data members shall be of literal type.
[Example 9: static_assert([](int n) { return [&n] { return ++n; }(); }(3) == 4); — _end example_]
A bit-field or a member of an anonymous union shall not be captured by reference.
An id-expression within the compound-statement of a lambda-expressionthat is an odr-use of a reference captured by reference refers to the entity to which the captured reference is bound and not to the captured reference.
[Note 8:
The validity of such captures is determined by the lifetime of the object to which the reference refers, not by the lifetime of the reference itself.
— _end note_]
[Example 10: auto h(int &r) { return [&] { ++r; };} — _end example_]
If a lambda-expression m2 captures an entity and that entity is captured by an immediately enclosing lambda-expression m1, thenm2's capture is transformed as follows:
- If m1 captures the entity by copy,m2 captures the corresponding non-static data member of m1's closure type; if m1 is not mutable, the non-static data member is considered to be const-qualified.
- If m1 captures the entity by reference,m2 captures the same entity captured by m1.
[Example 11:
The nested lambda-expressions and invocations below will output123234.
int a = 1, b = 1, c = 1;auto m1 = [a, &b, &c]() mutable { auto m2 = [a, b, &c]() mutable { std::cout << a << b << c; a = 4; b = 4; c = 4;}; a = 3; b = 3; c = 3; m2();}; a = 2; b = 2; c = 2; m1(); std::cout << a << b << c; — _end example_]
When the lambda-expression is evaluated, the entities that are captured by copy are used to direct-initialize each corresponding non-static data member of the resulting closure object, and the non-static data members corresponding to theinit-captures are initialized as indicated by the correspondinginitializer (which may be copy- or direct-initialization).
(For array members, the array elements are direct-initialized in increasing subscript order.)
These initializations are performed in the (unspecified) order in which the non-static data members are declared.
[Note 9:
This ensures that the destructions will occur in the reverse order of the constructions.
— _end note_]
[Note 10:
If a non-reference entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding lambda-expressionafter the lifetime of the entity has ended is likely to result in undefined behavior.
— _end note_]
An init-capture containing an ellipsis is a pack expansion that declares aninit-capture pack ([temp.variadic]).
[Example 12: template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm();auto lm2 = [...xs=std::move(args)] { return g(xs...); }; lm2();} — _end example_]