2

Consider the following piece of code:

void f() {
    int a = 3;
    [=]() {
        [=] () mutable {
            a = 5;
        }();
    }();
}

It compiles on Clang (https://godbolt.org/z/IEXotM) but not on GCC (https://godbolt.org/z/xWXFe6). Error for GCC:

<source>: In lambda function:

<source>:5:15: error: assignment of read-only variable 'a'

    5 |             a = 5;

      |             ~~^~~

Compiler returned: 1

According to https://en.cppreference.com/w/cpp/language/lambda,

Optional sequence of specifiers.The following specifiers are allowed:

mutable: allows body to modify the parameters captured by copy, and to call their non-const member functions

And it seems like that Clang's behavior here is correct. Is this the case?

Oblivion
  • 7,176
  • 2
  • 14
  • 33
Johnson Steward
  • 534
  • 3
  • 16
  • I suggest you create a bug report in GCC's bugzilla if one does not exist already. Chances are, in six years someone will even fix it. – bipll Oct 20 '19 at 10:55

2 Answers2

1

If you go read from the bottom of the link you cited you can read:

If a nested lambda m2 captures something that is also captured by the immediately enclosing lambda m1, then m2's capture is transformed as follows:

  • if the enclosing lambda m1 captures by copy, m2 is capturing the non-static member of m1's closure type, not the original variable or this.
  • if the enclosing lambda m1 captures by reference, m2 is capturing the original variable or this.

    #include <iostream>
    
    int main()
    {
    int a = 1, b = 1, c = 1;
    
    auto m1 = [a, &b, &c]() mutable {
        auto m2 = [a, b, &c]() mutable {
            std::cout << a << b << c << '\n';
            a = 4; b = 4; c = 4;
        };
        a = 3; b = 3; c = 3;
        m2();
    };
    
    a = 2; b = 2; c = 2;
    
    m1();                             // calls m2() and prints 123
    std::cout << a << b << c << '\n'; // prints 234
    }
    

So you are capturing the value from the enclosing lambda and to modify it you need to make it mutable in the enclosing lambda too.

Oblivion
  • 7,176
  • 2
  • 14
  • 33
  • It still doesn't make sense: "m2 is capturing the non-static member of m1's closure type, not the original variable or this.": what's wrong if m2 captures m1's closure type __by_value__, and then mutates it (as mutable is specified)? The example used double mutable since the enclosing lambda itself also modifies a, b, and c. – Johnson Steward Oct 20 '19 at 11:22
  • @JohnsonSteward You are modifying the value that belongs to m1 inside m2. it doesn't matter if you modify via a lambda or directly. If you modify the value that belongs to m1 anyhow you need to use mutable. – Oblivion Oct 20 '19 at 11:27
  • In the case I proposed, m2 is capturing __by value__. Or to say, m2 is not to modify m1's value (or to capture by reference), but to just keep a copy of that value (be it just the value or the entire closure of m1), so it doesn't make sense. – Johnson Steward Oct 22 '19 at 16:27
  • @JohnsonSteward check this https://wandbox.org/permlink/OtWoGXBLXiMAvkBW – Oblivion Oct 22 '19 at 16:48
  • @JohnsonSteward please read the part I highlighted. m2 does not capture the original variable. So you are modifying the value that belongs to m1. – Oblivion Oct 22 '19 at 17:58
  • http://eel.is/c++draft/expr.prim.lambda.capture#14 and http://eel.is/c++draft/expr.prim.lambda.capture#10, __the non-static member of m1's closure type__ should result in "an unnamed non-static data member is declared in the closure type", that is, in m2. This still does not explain why m1 need to have itself declared as mutable. – Johnson Steward Oct 22 '19 at 19:07
  • @JohnsonSteward "if m1 captures the entity by copy, m2 captures the corresponding non-static data member of m1's closure type;" – Oblivion Oct 22 '19 at 19:10
  • So? I expect that if m2 captures by copy, it should create a copy of that non-static data member. This would only require that m2 be declared mutable. – Johnson Steward Oct 22 '19 at 19:21
  • @JohnsonSteward it creates a copy, but NOT the copy of original variable. It copies the variable that belongs to m1. Now m1 sees its variable is being modified. Note that lambda captured by value needs to be mutable to modify the variable that it captured by value. – Oblivion Oct 22 '19 at 19:25
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/201301/discussion-between-johnson-steward-and-oblivion). – Johnson Steward Oct 23 '19 at 07:23
0

Specifying mutableon the top-level lambda fixes the GCC build: https://godbolt.org/z/qGGBgs

It seems that in GCC "captures" flow through lambdas. i.e. in the nested lambda captures can only capture what's captured in the above lambda. So if a isn't captured mutable in the top-level lambda, the nested lambda can't make it mutable all of the sudden.

I don't know if this is acceptable according to the C++ spec, though.

Mike.nl
  • 86
  • 2
  • But it's a by-value capture, and the inner lambda wouldn't be mutating a in the top-layer lambda (it's capturing it by value as well)... – Johnson Steward Oct 20 '19 at 10:50
  • Ah, right, fair point. GCC may not properly support this, then. Its behavior also doesn't make sense to me. – Mike.nl Oct 20 '19 at 10:53
  • @JohnsonSteward you need mutable even for by value capture: https://stackoverflow.com/questions/5501959/why-does-c11s-lambda-require-mutable-keyword-for-capture-by-value-by-defau – Oblivion Oct 20 '19 at 10:57