Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda expression with empty capture

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.

like image 568
Dusteh Avatar asked Mar 30 '17 13:03

Dusteh


People also ask

How do you capture variables in lambda?

Much like functions can change the value of arguments passed by reference, we can also capture variables by reference to allow our lambda to affect the value of the argument. To capture a variable by reference, we prepend an ampersand ( & ) to the variable name in the capture.

What is capture in lambda function?

A lambda expression can refer to identifiers declared outside the lambda expression. If the identifier is a local variable or a reference with automatic storage duration, it is an up-level reference and must be "captured" by the lambda expression.

Can lambda throw exception C++?

Throwing an exception from a lambda function is equivalent like throwing an exception from any other function.

How many ways we can capture the external variables in the lambda expression?

Explanation: There are three ways in which we can capture the external variables inside the lambda expression namely capture by reference, capture by value and capture by both that is mixed capture.


1 Answers

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

    // ...
}
like image 158
Barry Avatar answered Oct 03 '22 05:10

Barry