82

Consider the following example:

#include <cstdlib>

int main() {
    const int m = 42;
    [] { m; }(); // OK

    const int n = std::rand();
    [] { n; }(); // error: 'n' is not captured
}

Why do I need to capture n in the second lambda but not m in the first lambda? I checked section 5.1.2 (Lambda expressions) in the C++14 standard but I was unable to find a reason. Can you point me to a paragraph in which this is explained?

Update: I observed this behavior with both GCC 6.3.1 and 7 (trunk). Clang 4.0 and 5 (trunk) fails with an error in both cases (variable 'm' cannot be implicitly captured in a lambda with no capture-default specified).

eerorika
  • 232,697
  • 12
  • 197
  • 326
s3rvac
  • 9,301
  • 9
  • 46
  • 74
  • 2
    `constexpr` vs `const` – CinCout Apr 18 '17 at 08:28
  • 1
    clamg accepts the first one if you change `m;` to `m + 0;` – M.M Apr 18 '17 at 09:40
  • 4
    Same reason why `std::array` is OK and `std::array` is not. – n. m. could be an AI Apr 18 '17 at 09:57
  • 3
    I wish that variables weren't `constexpr` unless you explicitly specify it. If someone wants `constexpr` variable then one should declare it as one. – xinaiz Apr 18 '17 at 11:30
  • @BlackMoses I don't know c++, but it seems to me that it wouldn't make sense for `n` to be a regular `const`. If it were, wouldn't the compiler substitute the expression `std::rand()` wherever `n` is referenced, as indicated by @Beginner's answer? That wouldn't be the desired behavior by any stretch. – Brian McCutchon Apr 18 '17 at 16:07
  • 2
    @BlackMoses: `m` isn't `constexpr`, it's a compile-time constant integral expression... which was a thing long before the `constexpr` keyword was dreamt up. – Ben Voigt Apr 18 '17 at 16:57
  • @BrianMcCutchon That's not what `const` means... Where did you get that idea? – user253751 Apr 18 '17 at 22:25

3 Answers3

60

For a lambda at block scope, variables meeting certain criteria in the reaching scope may be used in limited ways inside the lambda, even if they are not captured.

Roughly speaking, reaching scope includes any variable local to the function containing the lambda, that would be in scope at the point the lambda was defined. So this includes m and n in the above examples.

The "certain criteria" and "limited ways" are specifically (as of C++14):

  • Inside the lambda, the variable must not be odr-used, which means it must not undergo any operation except for:
    • appearing as a discarded-value expression (m; is one of these), or
    • having its value retrieved.
  • The variable must either be:
    • A const, non-volatile integer or enum whose initializer was a constant expression, or
    • A constexpr, non-volatile variable (or a sub-object of such)

References to C++14: [expr.const]/2.7, [basic.def.odr]/3 (first sentence), [expr.prim.lambda]/12, [expr.prim.lambda]/10.

The rationale for these rules, as suggested by other comments/answers, is that the compiler needs to be able to "synthesize" a no-capture lambda as a free function independent of the block (since such things can be converted to a pointer-to-function); it can do this despite referring to the variable if it knows that the variable would always have the same value, or it can repeat the procedure for obtaining the variable's value independent of the context. But it can't do this if the variable could differ from time to time, or if the variable's address is needed for example.


In your code, n was initialized by a non-constant expression. Therefore n cannot be used in a lambda without being captured.

m was initialized by a constant expression 42, so it does meet the "certain criteria". A discarded-value expression does not odr-use the expression, so m; can be used without m being captured. gcc is correct.


I would say that the difference between the two compilers is that clang considers m; to odr-use m, but gcc does not. The first sentence of [basic.def.odr]/3 is quite complicated:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any non-trivial 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 is applied to e, or e is a discarded-value expression.

but upon reading closely it does specifically mention that a discarded-value expression does not odr-use the expression.

C++11's version of [basic.def.odr] originally did not include the discarded-value expression case, so clang's behaviour would be correct under the published C++11. However the text that appears in C++14 was accepted as a Defect against C++11 (Issue 712), so compilers should update their behaviour even in C++11 mode.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • I think [\[expr\]/11](https://timsong-cpp.github.io/cppwp/n4140/expr#11) means that the lvalue-to-rvalue conversion is not applied in this case. – cpplearner Apr 18 '17 at 09:54
  • @cpplearner I didn't see that earlier but I agree with you now that you pointed it out , thanks ... will update my answer – M.M Apr 18 '17 at 09:56
  • Reading again, it seems whether `m;` performs lvalue-to-rvalue conversion is irrelevant. The definition of _odr-use_ in [\[basic.def.odr\]/3](https://timsong-cpp.github.io/cppwp/n4140/basic.def.odr#3) says "either the lvalue-to-rvalue conversion is applied to `e`, or `e` is a discarded-value expression", and here the expression `m` is a discarded-value expression, so in the end the variable `m` is not odr-used. – cpplearner Apr 18 '17 at 10:33
  • @cpplearner Roger, I misread "discarded-value expression" as "unevaluated expression" – M.M Apr 18 '17 at 10:36
34

Its because it is a constant expression, the compiler treats is as if it were [] { 42; }();

The rule in [expr.prim.lambda] is:

If a lambda-expression or an instantiation of the function call operator template of a generic lambda odr-uses (3.2) this or a variable with automatic storage duration from its reaching scope, that entity shall be captured by the lambda-expression.

Here a quote from the standard [basic.def.odr]:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless applying the lvalue-to-rvalue conversion to x yields a constant expression (...) or e is a discarded-value expression.

(Removed not so important part to keep it short)

My simple understanding is: the compiler knows that m is constant at compile-time, whereas n will change at run-time and therefore n has to be captured. n would be odr-used, because you have to actually take a look at what is inside n at run time. In other words the fact that "there can be only one" definition of n is relevant.

This is from a comment by M.M:

m is a constant expression because it's a const automatic variable with constant expression initializer, but n is not a constant expression because its initializer was not a constant expression. This is covered in [expr.const]/2.7. The constant expression is not ODR-used, according to first sentence of [basic.def.odr]/3

See here for a demo.

Beginner
  • 5,277
  • 6
  • 34
  • 71
  • 5
    It would be good to explain in a bit more detail why `n;` *odr-uses* `n`, but `m;` does not *odr-use* `m` – M.M Apr 18 '17 at 08:41
  • 1
    I fail to see how that paragraph is relevant as it says that the entity _shall_ be captured by the lambda expression. In the first lambda, there is no capture, and yet GCC compiles it. – s3rvac Apr 18 '17 at 08:55
  • @s3rvac See comment by M.M The key here is that in one case there is odr-usage, but not in the other. – Ilya Popov Apr 18 '17 at 08:58
  • @IlyaPopov The variables are used in the same way in both lambdas, so I fail to see why there should be odr-usage in one lambda and not in the other lambda. – s3rvac Apr 18 '17 at 09:03
  • 2
    @s3rvac the paragraph says "*If* [conditions are met], that entity shall be captured". The conditions are met for `n` but not `m`. – M.M Apr 18 '17 at 09:09
2

EDIT: The previous version of my answer was wrong. Beginner's is correct, here is relevant standard quote:

[basic.def.odr]

  1. A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any non-trivial 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 is applied to e, or e is a discarded-value expression. ...

Since m is a constant expression, it is not odr-used and therefore does not need to be captured.

It appears that clangs behaviour is not compliant with the standard.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • The first one actually works, try it out here: http://ideone.com/o8WIO1 – Beginner Apr 18 '17 at 08:29
  • I believe this is ok, see the quote from the standard. How do you see it? – Beginner Apr 18 '17 at 08:35
  • @Beginner that quote actually confirms my answer. "shall" imposes a restriction on the program. It means that the variable must be captured. Since it was not captured (neither explicitly, nor implicitly by the use of default-capture), the program violates that rule. – eerorika Apr 18 '17 at 08:40
  • Automatic variables from the reaching scope which are constant expressions, do not need to be captured – M.M Apr 18 '17 at 08:42
  • 1
    @M.M that is interesting. Can you find the rule that says so? – eerorika Apr 18 '17 at 08:45
  • @user2079303 see the answer by "Beginner" – M.M Apr 18 '17 at 08:49
  • @M.M that quote doesn't by itself have an exception for constant expressions. Do you mean to imply that constant-expression-variables can not be odr-used? Or perhaps merely that `m` isn't odr used in *this* case. Beginner's quote doesn't explain this. – eerorika Apr 18 '17 at 08:57
  • Yeah I was hoping he would flesh it out a bit more... `m` is a constant expression because it's a `const` automatic variable with constant expression initializer, but `n` is not a constant expression because its initializer was not a constant expression. This is covered in [expr.const]/2.7. The constant expression is not ODR-used, according to first sentence of [basic.def.odr]/3 – M.M Apr 18 '17 at 09:08
  • @M.M I concede, `m` is not odr-used, and therefore no capture is required. – eerorika Apr 18 '17 at 09:30