Does the standard define what happens with this code?
#include <iostream>
template <typename Func>
void callfunc(Func f)
{
::std::cout << "In callfunc.\n";
f();
}
template <typename Func>
void callfuncref(Func &f)
{
::std::cout << "In callfuncref.\n";
f();
}
int main()
{
int n = 10;
// n is captured by value, and the lambda expression is mutable so
// modifications to n are allowed inside the lambda block.
auto foo = [n]() mutable -> void {
::std::cout << "Before increment n == " << n << '\n';
++n;
::std::cout << "After increment n == " << n << '\n';
};
callfunc(foo);
callfunc(foo);
callfuncref(foo);
callfunc(foo);
return 0;
}
The output of this with g++ is:
$ ./a.out
In callfunc.
Before increment n == 10
After increment n == 11
In callfunc.
Before increment n == 10
After increment n == 11
In callfuncref.
Before increment n == 10
After increment n == 11
In callfunc.
Before increment n == 11
After increment n == 12
Are all features of this output required by the standard?
In particular it appears that if a copy of the lambda object is made, all of the captured values are also copied. But if the lambda object is passed by reference none of the captured values are copied. And no copies are made of a captured value just before the function is called, so mutations to the captured value are otherwise preserved between calls.
The type of the lambda is simply a class (n3290 §5.1.2/3), with an
operator()
which executes the body (/5), and an implicit copy constructor (/19), and capturing a variable by copy is equivalent to copy-initialize (/21) it to a non-static data member (/14) of this class, and each use of that variable is replaced by the corresponding data member (/17). After this transformation, the lambda expression becomes only an instance of this class, and the general rules of C++ follows.That means, your code shall work in the same way as:
And it is obvious what
callfuncref
does here.The value is captured by copy unless you explicitly capture-all
[&]
or capture a specific variable by reference[&n]
. So the whole output is Standard.I find it easiest to understand this behaviour by manually expanding the lambda to a struct/class, which would be more or less something like this (as
n
is captured by value, capture by reference would look a bit different):Your functions
callfunc
andcallfuncref
operate more or less on objects of this type. Now let us examine the calls you do:Here you pass by value, so
foo
will be copied using the default copy constructor. The operations incallfunc
will only affect the internal state of the copied value, no state changed in the actual foo object.Same stuff
Ah, here we pass foo by reference, so
callfuncref
(which callsoperator()
) will update the actualfoo
object, not a temporary copy. This will result inn
offoo
being updated to11
afterwards, this is regularpass-by-reference
behaviour. Therefore, when you call this again:You will again operate on a copy, but a copy of
foo
wheren
is set to 11. Which shows what you expect.