Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 ]

like image 877
Jorge Israel Peña Avatar asked Nov 21 '15 23:11

Jorge Israel Peña


1 Answers

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.

like image 59
Barry Avatar answered Nov 14 '22 23:11

Barry