Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capturing a Lambda's static in a Nested Lambda

In this answer I use this code:

std::vector<std::vector<int>> imat(3, std::vector<int>(10));

std::for_each(imat.begin(), imat.end(), [&](auto& i) {
    static auto row = 0;
    auto column = 0;
    std::transform(i.begin(), i.end(), i.begin(), 
        [&](const auto& /*j*/) {
            return row * column++; 
    }); 

    ++row; 
});

But I notice some misbehavior in capturing static auto row depending upon the compiler.

Clang 3.7.0 yields:

0 0 0 0 0 0 0 0 0 0
0 1 2 3 4 5 6 7 8 9
0 2 4 6 8 10 12 14 16 18

gcc 5.1.0 yields:

0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0

And Visual Studio 2015 gives me a compile time error:

An internal error has occurred in the compiler.

If I change the capture nested capture to capture row explicitly I get the compiler error:

identifier in capture must be a variable with automatic storage duration declared in the reaching scope of the lambda

Am I allowed to capture a static in a nested lambda? It seems legit, but there are so many problems!

EDIT:

Fozi pointed out that I can get Visual Studio 2015 to compile and give the same output as Clang 3.7.0 if I change the nested lambda's parameter type from const auto& to const int&. Which seems completely unrelated, but it works.

This doesn't work if I try to capture row explicitly. In that case I still get the compiler error:

identifier in capture must be a variable with automatic storage duration declared in the reaching scope of the lambda

I've reported a Visual Studio 2015 bug here: https://connect.microsoft.com/VisualStudio/feedback/details/1930409/capturing-a-lambdas-static-in-a-nested-lambda

like image 888
Jonathan Mee Avatar asked Oct 22 '15 15:10

Jonathan Mee


People also ask

How do you capture a variable in lambda function?

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 clause in lambda function in C++?

Capture clause A lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. A lambda begins with the capture clause. It specifies which variables are captured, and whether the capture is by value or by reference.

Do lambdas go out of scope?

A lambda object must not outlive any of its reference captured objects. Lambda expressions may capture objects with automatic storage duration from the set of enclosing scopes (called the reaching scope) for use in the lambda's function body.

Are lambdas static?

A lambda or anonymous method may have a static modifier. The static modifier indicates that the lambda or anonymous method is a static anonymous function. A static anonymous function cannot capture state from the enclosing scope.


1 Answers

An Internal Compiler Error(ICE) is always a bug.

We don't need to capture variables of static storage duration but we do need to capture automatic variables that are odr-used. From the draft C++ standard section 5.1.2:

The lambda-expression’s compound-statement yields the function-body (8.4) of the function call operator, but for purposes of name lookup (3.4), determining the type and value of this (9.3.2) and transforming idexpressions referring to non-static class members into class member access expressions using (*this) (9.3.1), the compound-statement is considered in the context of the lambda-expression.

so row should be visible within the inner lambda and:

[...]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.[...]

Capture is only required for this and variables of automatic storage duration if they are odr-used and we can see that explicit capture is only defined for automatic variables or this:

The identifier in a simple-capture is looked up using the usual rules for unqualified name lookup (3.4.1); each such lookup shall find an entity. An entity that is designated by a simple-capture is said to be explicitly captured, and shall be this or a variable with automatic storage duration declared in the reaching scope of the local lambda expression.

For both Visual Studio and gcc to match the results of clang I can move row out to the global namespace, see it live for gcc. Also as Fozi points out changing const auto& /*j*/ to const int& /*j*/ makes it start working.

It looks like gcc accepts explicit capture of non-automatic variables as an extension and even then explicitly capturing row for example [&, &row](const auto & ) still produces all zeros.

Further for gcc if I move the definition for row to main then I see the following error (see it live):

/tmp/cchzwtQI.s: Assembler messages:
/tmp/cchzwtQI.s:1572: Error: symbol `_ZL3row' is already defined

Which seems like a compiler error to me.

I don't see any portion of the standard that would make the original program ill-formed. Nor should changing the auto to int make a difference and non of the changes introduced by polymorphic lambda proposal would seem to explain this difference either.

like image 106
Shafik Yaghmour Avatar answered Oct 04 '22 00:10

Shafik Yaghmour