Why does C++11's lambda require mutable keyword for capture-by-value, by default?
It requires mutable
because by default, a function object should produce the same result every time it's called. This is the difference between an object orientated function and a function using a global variable, effectively.
Why is 'mutable' a lambda function attribute, instead of being a capture type?
There is a mention about your suggestion in n2651:
The syntax for lambda expressions could be extended to allow declaring
whether the closure members should be declared mutable or not.This approach could be confusing to programmers, as the mutability is not a
property of the closure object, but rather the variables stored in the
closure.
I don't know if this is the only reason, but it does seem like it was considered.
However, in Herb Sutter's proposal, he suggests getting rid of mutable
and not making the capture copies implicitly const
, so we might see changes again.
C++ Lambdas: Difference between mutable and capture-by-reference
What is happening
The first will only modify its own copy of x
and leave the outside x
unchanged.
The second will modify the outside x
.
Add a print statement after trying each:
a();
std::cout << x << "----\n";
b();
std::cout << x << '\n';
This is expected to print:
6
5
----
6
6
Why
It may help to consider that lambda
[...] expressions provide a concise way to create simple function objects
(see [expr.prim.lambda] of the Standard)
They have
[...] a public inline function call operator [...]
which is declared as a const
member function, but only
[...] if and only if the lambda expression’s parameter-declaration-clause is not followed by
mutable
You can think of as if
int x = 5;
auto a = [=]() mutable { ++x; std::cout << x << '\n'; };
==>
int x = 5;
class __lambda_a {
int x;
public:
__lambda_a () : x($lookup-one-outer$::x) {}
inline void operator() { ++x; std::cout << x << '\n'; }
} a;
and
auto b = [&]() { ++x; std::cout << x << '\n'; };
==>
int x = 5;
class __lambda_b {
int &x;
public:
__lambda_b() : x($lookup-one-outer$::x) {}
inline void operator() const { ++x; std::cout << x << '\n'; }
// ^^^^^
} b;
Q: But if it is a const
function, why can I still change x
?
A: You are only changing the outside x
. The lambda's own x
is a reference, and the operation ++x
does not modify the reference, but the refered value.
This works because in C++, the constness of a pointer/reference does not change the constness of the pointee/referencee seen through it.
Why can we avoid specifying the type in a lambda capture?
From cppreference:
A capture with an initializer acts as if it declares and explicitly captures a variable declared with type auto, whose declarative region is the body of the lambda expression (that is, it is not in scope within its initializer), [...]
Lambdas used the opportunity of a syntax that was anyhow fresh and new to get some things right and allow a nice and terse syntax. For example lambdas operator()
is const
and you need to opt-out via mutable
instead of the default non-const of member functions.
No auto
in this place does not create any issues or ambiguities. The example from cppreference:
int x = 4;
auto y = [&r = x, x = x + 1]()->int
{
r += 2;
return x * x;
}(); // updates ::x to 6 and initializes y to 25.
From the lambda syntax it is clear that &r
is a by reference capture initialized by x
and x
is a by value capture initialized by x + 1
. The types can be deduced from the initializers. There would be no gain in requiring to add auto
.
In my experience n could have been just declared inside the lambda body with auto or int as datatype. Isnt it?
Yes, but then it would need to be static
. This produces the same output in your example:
std::generate(v.begin(), v.end(), [] () mutable {
static int n = 0;
return n++; });
However, the capture can be considered cleaner than the function local static.
Why do these lambda captured values have different types?
The behavior of Clang, GCC and MSVC in C++20 mode is correct for all standard versions supporting lambdas. decltype(i)
and decltype(ri)
yield the type of the named variables. It is not rewritten to refer to the members of the closure object, as would be the case for decltype((ri))
. (see e.g. [expr.prim.lambda.capture]/14 in C++17 draft N4659 handling this specifically)
Apparently MSVC's default behavior for standard modes before C++20 is non-conforming in how lambdas are handled. According to the documentation the flag /Zc:lambda
must be given to handle lambdas standard-conforming. With this option MSVC produces the same result as in C++20 mode for C++17 and C++14 mode as well.
See for example this bug report which was closed as not-a-bug with instruction to use this flag. Also note that it doesn't seem to be included in /permissive-
.
Related Topics
Purpose of Returning by Const Value
Reason to Pass a Pointer by Reference in C++
Where Does Visual Studio Look For C++ Header Files
Right Way to Split an Std::String into a Vector≪String≫
Boolean Expression (Grammar) Parser in C++
Is Ncurses Available For Windows
Visual C++ Equivalent of Gcc'S _Attribute_ ((_Packed_))
What Is the Windows Equivalent For En_Us.Utf-8 Locale
When Should You Use Constexpr Capability in C++11
Const VS Constexpr on Variables
How to Get Rid of 'Deprecated Conversion from String Constant to 'Char*'' Warnings in Gcc
Global Memory Management in C++ in Stack or Heap
What Are the Gcc Default Include Directories
Why Is This Program Erroneously Rejected by Three C++ Compilers