I thought I understood how capture by reference works in C until I faced this situation:
auto inrcrementer = []() {
int counter = 0;
return [&counter]() {
return counter ;
};
};
int main() {
auto inc = inrcrementer();
cout << inc() << ", " << inc() << ", " << inc() << ", " << endl;
return 0;
}
I was expecting this to return 0, 1, 2 but it returns 0, 32765, 32765. Why?
Moreover, if I change it to this:
auto inrcrementer = []() {
int counter = 0;
return [counter]() mutable {
return counter ;
};
};
int main() {
auto inc = inrcrementer();
cout << inc() << ", " << inc() << ", " << inc() << ", " << endl;
return 0;
}
It is fixed and it returns as expected. What is the difference between the two?
CodePudding user response:
After expanding the outer lambda, your first example can be written as sth like this:
struct incrementer
{
auto operator()()
{
int counter = 0;
return [&counter]() { return counter ;}
}
};
As it's become more visible now counter is a local variable and the inner lambda operates on a reference to local object, a dangling one at the call site.
The second example can be expanded to sth like this:
struct incrementer
{
auto operator()()
{
int counter = 0;
struct inner
{
inner(int cnt) : mcnt{cnt}();
int operator()() { return mcnt ; }
int mcnt;
};
return inner{counter};
}
};
Basically, the lifetime of the counter needs to be preserved for the entire lifetime of the lambda, one way or another.
Yet another way is to store the counter in the outer lambda, e.g.
auto inrcrementer = [counter=0]() mutable {
return [&counter]() {
return counter ;
};
};
This is fine as long as the inner one does not outlive the outer one.
CodePudding user response:
The problem is that in your first example, the lambda stores a reference to, rather than a copy of, counter. But counter goes out of scope when incrementer returns so that reference is left dangling. Then, when you invoke the lambda returned by incrementer, that reference is invalid.
What this usually means in practise is that it is pointing at an area of the stack that has been / is being used for something else. That's why you get weird results.
In your second example, the lambda has its own copy of counter, and that remains in scope for as long as the lambda remains in existence. Since you have declared the lambda mutable, it can do whatever it likes with its (private) copy of counter without anybody else messing with it.
So capture by reference can be dangerous. Use with care! A reference is just a pointer really, under the skin, but because it doesn't look like one it's easy to slip up.
