In the C++14 standard § 5.1.2/12 it shows an example of a lambda expression that apparently seems to be able to refer to a reaching scope's variable x
, even though:
x
"Here's the example:
void f(int, const int (&)[2] = {}) { } // #1
void test() {
const int x = 17;
auto g = [](auto a) {
f(x); // OK: calls #1, does not capture x
};
}
See that it does compile. It seems to hinge on x
being const
; if the const
is removed, it no longer compiles for the reasons one would expect (capture list is empty). It happens even if I make the parameter be int
so that it's no longer a generic lambda.
How is it possible for the lambda to refer to x
even though the capture list is empty? And how is this possible while at the same time apparently not capturing x
(as the comment says)?
The closest thing I found on this subject was someone else tangentially noticing this in a comment.
Here's the full section 5.1.2/12 from the standard:
A lambda-expression with an associated capture-default that does not explicitly capture
this
or a variable with automatic storage duration (this excludes any id-expression that has been found to refer to an init-capture’s associated non-static data member), is said to implicitly capture the entity (i.e.,this
or a variable) if the compound-statement:
- odr-uses (3.2) the entity, or
- names the entity in a potentially-evaluated expression (3.2) where the enclosing full-expression depends on a generic lambda parameter declared within the reaching scope of the lambda-expression.
[ Example:
void f(int, const int (&)[2] = {}) { } // #1 void f(const int&, const int (&)[1]) { } // #2 void test() { const int x = 17; auto g = [](auto a) { f(x); // OK: calls #1, does not capture x }; auto g2 = [=](auto a) { int selector[sizeof(a) == 1 ? 1 : 2]{}; f(x, selector); // OK: is a dependent expression, so captures x }; }
—end example ] All such implicitly captured entities shall be declared within the reaching scope of the lambda expression. [ Note: The implicit capture of an entity by a nested lambda-expression can cause its implicit capture by the containing lambda-expression (see below). Implicit odr-uses of this can result in implicit capture. —end note ]
You have the right quote. A variable needs to be captured if it is odr-used. ODR-use means basically that the variable is used in a context where it needs a definition. So either its address is taken, or a reference is taken to it, etc. One key exception is, from [basic.def.odr]:
A variable
x
whose name appears as a potentially-evaluated expressionex
is odr-used byex
unless applying the lvalue-to-rvalue conversion (4.1) tox
yields a constant expression (5.20) that does not invoke any nontrivial functions and, ifx
is an object,ex
is an element of the set of potential results of an expressione
, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause 5).
So in your example, applying lvalue-to-rvalue conversion on x
yields a constant expression (since x
is a constant integral), so it's not odr-used. Since it's not odr-used, it doesn't have to be captured.
On the other hand, if x
were bound to a reference (e.g. f
took its argument as const int&
), then it would be odr-used, and so would have to be captured. In the second example presented, x
's "odr-use-ness" is dependent on what the generic lambda argument is, so that is considered captured anyway for sanity's sake.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With