13

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

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • @865719 Hmmm, I can't seem to find the fix you're talking about, but I'm gonna update my Visual Studio 2015 and try again. – Jonathan Mee Oct 22 '15 at 15:49
  • @865719 My Visual Studio 2015 is up to date according to Windows Update. Incidentally I'm also able to replicate the bug by pasting the code into: http://webcompiler.cloudapp.net/ – Jonathan Mee Oct 22 '15 at 15:53
  • 1
    It compiles if you change `const auto& /*j*/` to `const int& /*j*/` – Fozi Oct 22 '15 at 16:00
  • @Fozi Excellent call. I have no idea how you found that it seems completely unrelated. I added an edit to my question. Seems like this is still a bug with the captures. – Jonathan Mee Oct 22 '15 at 16:14
  • GCC 5.2.0 compiles it correctly. – Jason Oct 22 '15 at 16:58
  • @Jason Compiles and runs it correctly? That's probably additional useful information. I wonder what changed between the minor versions? – Jonathan Mee Oct 22 '15 at 17:15
  • Actually, GCC 5.2.0 is also incorrect (http://goo.gl/4HqHQR). It's missing the `inc`. – Jason Oct 22 '15 at 18:04
  • @Jason Yeah I just ran it here: http://melpon.org/wandbox/ I'm getting the same result as gcc 5.1.0. The full matrix of 0s. – Jonathan Mee Oct 22 '15 at 18:47
  • 1
    For VS2013, it seems get correct result if change `[&](auto& i)` to `[&](std::vector& i)` – zangw Oct 23 '15 at 02:58
  • @zangw You also have to change the nested lambda's parameter type from `const auto& /*j*/` to `const int& /*j*/` right? I didn't think that Visual Studio 2013 supported `auto` lambda parameters. – Jonathan Mee Oct 23 '15 at 10:46
  • 1
    @JonathanMee, you are right. I have changed the parameter type as you said. – zangw Oct 23 '15 at 11:06
  • Still a bug in VS2017 Update 4, as can be seen with this demo code: https://godbolt.org/g/hnJwwy – w1th0utnam3 Dec 04 '17 at 20:06

1 Answers1

2

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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • Did you see the [comment @Fozi made](http://stackoverflow.com/questions/33285103/capturing-a-lambdas-static-in-a-nested-lambda/33289327#comment54371126_33285103)? This code runs and produces the correct result in Visual Studio 2015: `for_each(imat.begin(), imat.end(), [&](auto& i) { static auto row = 0; auto column = 0; transform(i.begin(), i.end(), i.begin(), [&](const int& /*j*/) { return row * column++; }); ++row; });` – Jonathan Mee Oct 22 '15 at 19:54
  • Can you remind me what "variables that are odr-used" means? That's One Definition Rule? Wouldn't all variables be odr-used then? – Jonathan Mee Oct 22 '15 at 19:55
  • 1
    @JonathanMee "odr-used" is roughly equivalent to "necessitates the entities existence at runtime". – Columbo Oct 22 '15 at 20:34
  • 1
    @JonathanMee I added a link to my answer with an odr-use reference. Since we don't need to capture static variables it does not matter for them. – Shafik Yaghmour Oct 23 '15 at 00:40
  • 1
    @JonathanMee you know Fozi was correct, it seems that webcompiler times out a lot and I was mistaking that for failure but after trying several times it did work with that change. – Shafik Yaghmour Oct 23 '15 at 00:40
  • @ShafikYaghmour You say "we don't need to capture `static` variables." Does that mean file scope `statics` or like function scope `statics`? – Jonathan Mee Oct 23 '15 at 01:30