1

If I understand correctly (my source is page 395 of C++ Primer, 5th edition), mutable makes it possible to modify the captured variable from within the lambda. Two things I do not understand:

  1. How is this different from capturing by reference? And, if capturing by reference is what's going on underneath, then what is the purpose of mutable? Just a syntactic sugar?

  2. Wouldn't it be more helpful is mutable could be applied on per-variable basis?

AlwaysLearning
  • 7,257
  • 4
  • 33
  • 68
  • 1. is answered here http://stackoverflow.com/questions/5501959/ 2. should be clear, too, afterwards – WorldSEnder Jul 09 '15 at 12:32
  • Stop thinking that mutable is (only) related to lambdas. – deviantfan Jul 09 '15 at 12:41
  • @deviantfan I do not understand what you mean. When an object is `const`, its methods can modify the `mutable` data members. But there I choose which data members to declare `mutable`, while here it's all or none! – AlwaysLearning Jul 09 '15 at 12:55
  • @WorldSEnder In the post that you cited, they explain how all the different options work (and I knew that a `lambda` is basically an object of an unnamed class, so that wasn't new to me). However, I did not see an answer to the question: why would I use mutable instead of `[&]`? I looks like I am missing something very basic, but I do not know what it is... – AlwaysLearning Jul 09 '15 at 12:58
  • @MeirGoldenberg, the when using `[&]` you change the variable of the scope you bound the lambda. When using `[=] mutable` you copy the variable first, but you can mutate it in the lambda – WorldSEnder Jul 09 '15 at 13:01
  • Got it! The captured variable is `const` by default! `mutable` removes that `const`ness, but does not change the fact that the variable is passed by value! – AlwaysLearning Jul 09 '15 at 13:04

1 Answers1

5

mutable does allow you to modify variable which are defined outside of the scope of the lambda, but any change you make to these variable is not spread to the initial variable:

auto f1 = [=] () mutable { n += 4; return n ; } ;
auto f2 = [&] () { n += 4; return n ; } ;

std::cout << f1 () << ' ' ;   // Call to f1, modify n inside f1
std::cout << n << ' ' ;       // But not in main

// 2nd call to f1, the value of n inside f1 is the value stored during the first call, 
// so  8 (the output will be 8 + 4)
std::cout << f1 () << ' ' ;   

std::cout << f2 () << ' ' ;   // Call to f2, modify n inside f2
std::cout << n << std::endl ; // And in main

Output

8 4 12 8 8 

Another difference is that when you use capture by value, the value are captured when the lambda is evaluated, not when it is called:

int n = 4 ;
auto f1 = [=] () mutable { return n ; } ; // lambda evaluated, value is 4
auto f2 = [&] () { return n ; } ; // lambda evaluated, reference to n
std::cout << f1 () << ' ' ; // Call to f1, captured value is 4
std::cout << f2 () << ' ' ;
n = 8 ;
std::cout << f1 () << ' ' ; // Call to f1, captured value is still 4
std::cout << f2 () << std::endl ; // Reference to n, so updated value printed

Output:

4 4 4 8 

One last (huge) difference is that variable captured by reference are not available once their target is out of scope:

std::function <int ()> f (bool withref) {
    int n = 4 ;
    if (withref) {
        return [&] () { return n ; } ;
    }
    return [=] () { return n ; } ;
}

auto f1 = f (false) ;
auto f2 = f (true) ;

std::cout << f1 () << ' ' ;
std::cout << f2 () << std::endl ; // Something will go wrong here...

Output:

4 1639254944 

The second behaviour is undefined.


To summarize:

  • Using mutable allow you to modify variable captured by value inside the scope of your function, but since the variable have been copied to another memory location when the lambda has been evaluated, you are only modifying a local copy of the variable (and you have access to this copy, even when the original variable has vanished).
  • Using & does not create a copy of the original variable(s), allowing you to modify it (the original one), but providing you to access this variable after its « destruction ».
Holt
  • 36,600
  • 7
  • 92
  • 139
  • Thank you! You really hit the nail on the head! – AlwaysLearning Jul 09 '15 at 13:05
  • It would be even more educational if you made `f1` increment `n` e.g. `n += 4` and called `f1()` twice! – AlwaysLearning Jul 09 '15 at 13:14
  • @MeirGoldenberg Yes, you are right it clearly shows the fact that there is a copy of `n` associated to `f1` (kind of a `static` variable with an initial value). I updated my answer. – Holt Jul 09 '15 at 13:21
  • It's not a static variable. It's a consequence of the fact that the captured variable is a data member of the `lambda` object. – AlwaysLearning Jul 09 '15 at 13:29
  • 1
    @MeirGoldenberg I did not say it was, simply that the behaviour is (almost) the same as doing `[_n = n] () { static int n = _n ; n += 4 ; return n ; }` without a `mutable` keyword. – Holt Jul 09 '15 at 13:34
  • Nice. Using `static` in a `lambda` is refreshing. – AlwaysLearning Jul 09 '15 at 13:47