简体   繁体   中英

lambda capture in C++17

[expr.prim.lambda.capture]/7 :

If an expression potentially references a local entity within a scope 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 captured by each intervening lambda-expression with an associated capture-default that does not explicitly capture it. The implicit capture of *this is deprecated when the capture-default is =; see [depr.capture.this]. [Example 4:

void f(int, const int (&)[2] = {});
void test() {
  const int x = 17;
  auto g = [](auto a) {
    f(x);                       // OK, calls #1, does not capture x
  };

  auto g1 = [=](auto a) {
    f(x);                       // OK, calls #1, captures x
  };
}

... Within g1, an implementation can optimize away the capture of x as it is not odr-used. — end example]

So an entity is captured even if it is not odr-used by the lambda body, and the example below says that the implementations can optimize it away. Therefore, since the implementations can optimize it away, why add such a rule? What's the point of it?

cppreference says it was added in C++17. What's the original proposal?

P0588R0 contains the rationale explaining why the rules for implicit capture were changed in order to capture some variables that are not going to be odr-used anyway. It's very subtle.

Basically, in order to determine the size of a lambda closure type, you need to know which variables are captured and which ones are not, but under the old rules:

  • in order to determine whether a variable is captured implicitly, you have to know whether it's odr-used, and
  • in order to know whether the variable is odr-used, you have to perform substitution into the lambda's body, and
  • if the function call operator is generic, it means the above substitution is done at a point where the function call operator itself is not ready to be instantiated yet (since its own template parameters aren't yet known). This causes problems that could be avoided by not doing the substitution in the first place.

The paper gives the following example:

template<typename T> void f(T t) {
    auto lambda = [&](auto a) {
        if constexpr (Copyable<T>() && sizeof(a) == 32) {
            T u = t;
        } else {
            // ... do not use t ...
        }
    };
    // ...
}

When f is instantiated with a non-copyable type, ideally we would like the branch with T u = t; to be discarded. That's what the if constexpr is there for. But an if constexpr statement does not discard the un-taken branch until the point at which the condition is no longer dependent---meaning that the type of a must be known before T u = t; can be discarded. Unfortunately, under the old rules, T must be substituted into T u = t; in order to determine whether t is captured, and this happens before any opportunity to discard this statement

The new rules simply declare that t is captured; therefore, the compiler doesn't have to perform any substitution into the lambda body until the point at which a specialization of the function call operator is referenced (and thus, its body can be fully instantiated). And if the compiler is somehow able to prove that t will never be odr-used by any possible specialization of the function call operator, it's free to optimize it out.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM