[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:

[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:

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

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:

[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_]