[expr.prim.lambda.capture] (original) (raw)
7 Expressions [expr]
7.5 Primary expressions [expr.prim]
7.5.5 Lambda expressions [expr.prim.lambda]
7.5.5.2 Captures [expr.prim.lambda.capture]
lambda-capture: capture-default capture-list capture-default , capture-list
capture-default: & =
capture-list: capture capture-list , capture
capture: simple-capture init-capture
simple-capture: identifier ... & identifier ... this * this
init-capture: ... identifier initializer & ... identifier initializer
The body of a lambda-expression may refer to variables with automatic storage duration and the *this object (if any) of enclosing block 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
:
The form [&,this] is redundant but accepted for compatibility with ISO C++ 2014.
— end note
]
Ignoring appearances ininitializers of init-captures, an identifier orthis shall not appear more than once in alambda-capture.
[ Example
:
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
:
void f() {
int x = 0;
auto g = [x](int x) { 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 ;” whose declarative region is the lambda-expression'scompound-statement, 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
:
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
:
int x = 4; auto y = &r = x, x = x+1->int { r += 2; return x+2; }();
auto z = [a = 42](int a) { return 1; };
— 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
:
This occurs even if overload resolution selects a static member function for the id-expression.
— end note
] - A this expression potentially references *this.
- A lambda-expression potentially references the local entities named by its simple-captures.
If an expression potentially references a local entity within a declarative region in which it is odr-usable, 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
:
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 might optimize away the capture of x as it is not odr-used.
— end example
]
[ Note
:
The set of captured entities is determined syntactically, and entities might be implicitly captured even if the expression denoting a local entity is within a discarded statement ([stmt.if]).
[ Example
:
template
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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 captures the entity by reference,m2 captures the same entity captured by m1.
[ Example
:
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
:
This ensures that the destructions will occur in the reverse order of the constructions.
— end note
]
[ Note
:
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
]
[ Example
:
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
]