17

I've encountered an interesting case (at least for me) when using lambdas and was wondering whether it is a compiler bug or something allowed by the standard feature.

Let's cut to the chase. Having sample code:

const int controlValue = 5;
std::vector<int> vect{ 0, 1, 2, 3 };
const auto result = std::any_of(vect.begin(), vect.end(), [](const int& item)
{
    return item == controlValue;
});

Notice that controlValue variable is not captured by the lambda expression. Additionally, in the cppreference for lambda expressions it is stated that [] - captures nothing

Using VS2015 for compilation of the above code gives an error which is not surprising:

error C3493: 'controlValue' cannot be implicitly captured because no default capture mode has been specified

However, when using MinGW with gcc 4.8.2 same example compiles and works. Some online compilers including gcc 5.4.0, clang 3.8.0 give similar result.

When controlValue loses its const then all tested compilers give the error all expect (that the variable is not captured which is fine).

Which of the compilers is standard compliant in this case? Does this mean that some optimizations or other "hacks" are used for const variables here? Maybe something is captured implicitly? Could anyone explain the situation happening here?

EDIT:

Some pointed out that this question is a duplicate of Lambda capturing constexpr object . While the answer there may be somewhat related (points to the odr-use case) the question there is about an error occurring while capturing by ref. The topic here is quite different and focuses on not capturing explicitly a variable at all (although using it in the lambda body).

After looking through more lambda related questions, if someone's interested, I'd point to Using lambda captured constexpr value as an array dimension which (same as @Barry stated) suggests VS2015 bug and shows that setting the controlValue variable in the example here to static fixes the compilation under VS2015.

Community
  • 1
  • 1
Dusteh
  • 1,496
  • 16
  • 21
  • Note that `controlValue` is a `constexpr` value. – Jarod42 Mar 30 '17 at 13:42
  • Why is it considered a `constexpr` here? Could you explain a bit more or point me to some docs? I believed that `constexpr` can be considered `const` in some cases but not the other way around – Dusteh Mar 30 '17 at 13:44
  • G++ 6.3.0 has no problem with your code, and neither has Apple's Clang. It does not answer your question, but at least underlines the relevance of your question. – Chiel Mar 30 '17 at 13:46
  • 1
    closely related to [Lambda capturing constexpr object](http://stackoverflow.com/q/13468989/3953764) – Piotr Skotnicki Mar 30 '17 at 13:51
  • Compare also to `const int controlValue = f();` – aschepler Mar 30 '17 at 13:56
  • See [here](http://eel.is/c++draft/expr.prim.lambda#capture-7). That's probably because you are not odr-using it, therefore it doesn't capture it actually. That section of the working draft also contains an example that is really close to what you did. – skypjack Mar 30 '17 at 13:58
  • Possible duplicate of [Lambda capturing constexpr object](http://stackoverflow.com/questions/13468989/lambda-capturing-constexpr-object) – Hatted Rooster Mar 30 '17 at 14:03
  • 1
    @Dusteh: In that particular case, you can even do `const int controlValue = 5; std::array a;` – Jarod42 Mar 30 '17 at 14:33
  • 1
    Have a look at [this answer](http://stackoverflow.com/a/40425486/3876684). – Claas Bontus Mar 31 '17 at 10:47

1 Answers1

16

This is a VS bug. The code is perfectly well-formed.

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.

Where a variable is odr-used if, according to [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 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 (4.1) is applied to e, or e is a discarded-value expression (Clause 5).

And, from [expr.const]:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions: [...] an lvalue-to-rvalue conversion (4.1) unless it is applied to a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression

In:

return item == controlValue;

controlValue is a glvalue of integral type that refers to a complete non-volatile const object initialized with a constant expression. Hence, when we use controlValue in a context that involves an lvalue-to-rvalue conversion, it is not odr-used. Since it's not odr-used, we don't need to capture it.

When you changed controlValue to be non-const, it's ceases to be a constant expression, and the equality check odr-uses it. Since it's not captured but is odr-used, the lambda is ill-formed.


Note that exactly such an example appears in the standard:

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
    };

    // ...
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 3
    A workaround, of using a `using controlValue_t = std::integral_constant;`, then using `controlValue_t{}` or `controlValue_t::value` within lambda, might be worth mentioning. Of course, with MSVC, I am reluctant to suggest a workaround without testing it. ;) – Yakk - Adam Nevraumont Mar 30 '17 at 14:58
  • Thanks, this looks helpfull. I just need some time to digest it and read about odr-uses in this context :) – Dusteh Mar 31 '17 at 08:59