Jorge Israel Peña Jorge Israel Peña - 4 years ago 124
C++ Question

how is this lambda with an empty capture list able to refer to reaching-scope name?

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:


  1. the capture list is empty, i.e. no capture-default

  2. the comment says that it "does not capture
    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 ]

Answer Source

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 expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) that does not invoke any nontrivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, 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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download