lambda capture by value mutable doesn't work with const &? - c++

Consider the following:
void test( const int &value )
{
auto testConstRefMutableCopy = [value] () mutable {
value = 2; // compile error: Cannot assign to a variable captured by copy in a non-mutable lambda
};
int valueCopy = value;
auto testCopyMutableCopy = [valueCopy] () mutable {
valueCopy = 2; // compiles OK
};
}
Why is the first version a compile error when I've declared the lambda as mutable and captured value by value (which I thought made a copy of it)?
Tested with clang (x86_64-apple-darwin14.3.0), which is where the error message comes from, and Visual C++ (vc120).

[C++11: 5.1.2/14]: An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that does not include an &. For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise. [..]
The type of value inside your lambda is const int, because it was captured by copy from a const int&.
Thus, even though the lambda's call operator function is not const (you marked the lambda mutable), the actual implicit member value is of type const int and cannot be mutated.
Frankly, this seems absurd; I would expect this rule to say that the referenced type loses constness, as it's a copy. The presence or absence of the mutable keyword on the lambda itself (and, thus, the presence or absence of the const keyword on the generated call operator function) should be the only access control here.
In C++14 you can work around this by capturing as [value=value], which uses the same rules as auto and thus drops the const. C++'s great, ain't it?

mutable allows a lambda to modify copy of a non-const parameter captured by copy, but it does not allow it for const parameters.
So this code works (and outputs inside 2 outside 1):
int a = 1;
[a]() mutable {
a = 2; // compiles OK
cout << "inside " << a << "\n";
}();
cout << " outside " << a << "\n";
But if we omit mutable, or make a const int, the compiler gives an error.
In our case, the first lambda gives an error because value is const:
void test( const int &value )
If we make copyValue const:
const int valueCopy = value;
then the same error will occur with the second lambda.

Related

error: no matching member function for call to 'push_back' only instde std::for_each [duplicate]

I have a program as below:
int main()
{
int val = 4;
auto add = [val](int a)->int{
val += 2;
return a+val;
};
cout << add(3) << endl;
cout << val << endl;
return 0;
}
There's a compiling error in Xcode: Cannot assign to a variable captured by copy in a non-mutable lambda.
My question is: if we choose to use the copy (using "=" or value name), can't this value be assigned a new value or changed?
Inside a lambda, captured variables are immutable by default. That doesn't depend on the captured variables or the way they were captured in any way. Rather, the function call operator of the closure type is declared const:
This function call operator or operator template is declared const
(9.3.1) if and only if the lambda-expression’s
parameter-declaration-clause is not followed by mutable.
Therefore, if you want to make the captured variables modifiable inside the body, just change the lambda to
auto add = [val] (int a) mutable -> int {
val += 2;
return a+val;
};
so the const-specifier is removed.
The operator () of a lambda is implicitly const unless the lambda is declared mutable - and you can't modify the data members in a const member function. This happens regardless of the type of the capture.
Just capture it by reference, it will work !!
auto add = [&val](int a) -> int{
//
}

Why default capture is not consistently const for both local variables and member variables?

I'm curious what's the story behind following inconsistency with passing default parameters:
struct Example {
void run() {
int localVar = 0;
auto l = [=](){
// localVar = 100; Not allowed (const copy of localVar)
memberVar = 100; // allowed (const copy of this pointer - NOT const *this copy)
};
l();
}
int memberVar = 1;
};
Why not pass all parameters to lambda capture by const value (including const *this)?
Is that a desirable design choice, or result of a implementation limitation?
EDIT:
I know const pointer to the object is passed as a parameter and the object itself can be modified but the pointer itself cannot. But this is implementation detail that has to be known to the reader and is not obvious from the first look. Consistent from my subjective perspective would be capturing *this by const value...
Why default capture is not consistently const for both local variables and member variables?
Because member variables aren't captured at all by a default capture. What is captured is this pointer. And that is "const": You cannot modify this. But in a non-const member function it is a pointer to non-const and thus you can modify the non-const members.
You're right, this is a lame behavior.
That's why in C++20 the implicit capture of this (i.e. by reference) is deprecated when the capture-default is =.
Presumably the intent is to change = one day to capture *this (i.e. by value).

Passing by constant reference in the lambda capture list

I'm building a lambda function that requires access to a fair number of variables in the context.
const double defaultAmount = [&]{
/*ToDo*/
}();
I'd rather not use [=] in the list as I don't want lots of value copies to be made.
I'm concerned about program stability if I use [&] since I don't want the lambda to modify the capture set.
Can I pass by const reference? [const &] doesn't work.
Perhaps a good compiler optimises out value copies, so [=] is preferable.
You can create and capture const references explicitly:
int x = 42;
const int& rx = x;
auto l = [&rx]() {
x = 5; // error: 'x' is not captured
rx = 5; // error: assignment of read-only reference 'rx'
};
The capture list is limited in what can be captured; basically by-value or by-reference (named or by default), the this pointer and nothing.
From the cppreference;
capture-list - a comma-separated list of zero or more captures, optionally beginning with a capture-default. Capture list can be passed as follows (see below for the detailed description):
[a,&b] where a is captured by value and b is captured by reference.
[this] captures the this pointer by value
[&] captures all automatic variables odr-used in the body of the lambda by reference
[=] captures all automatic variables odr-used in the body of the lambda by value
[] captures nothing
You could create local const& to all the object you wish to capture and use those in the lambda.
#include <iostream>
using namespace std;
int main()
{
int a = 5;
const int& refa = a;
const int b = [&]() -> int {
//refa = 10; // attempts to modify this fail
return refa;
}();
cout << a << " " << b << endl;
}
The capture could be either for all the references, or an explicit list what is required;
const int b = [&refa]()
Another alternative is not to capture the local variables at all. You then create a lambda that accepts as arguments the variables you need. It may be more effort as the local variable count grows, but you have more control over how the lambda accepts its arguments and is able to use the data.
auto lambda = [](const int& refa /*, ...*/) { */...*/ }
lambda(...);
Sadly the C++11 grammar does not allow for this, so no.
You can capture a constant reference to an object, not an object itself:
A a;
const A& ref_a = a;
const double defaultAmount = [&]{
ref_a.smth();
}();

C++ lambda copy value in capture-list

I have a program as below:
int main()
{
int val = 4;
auto add = [val](int a)->int{
val += 2;
return a+val;
};
cout << add(3) << endl;
cout << val << endl;
return 0;
}
There's a compiling error in Xcode: Cannot assign to a variable captured by copy in a non-mutable lambda.
My question is: if we choose to use the copy (using "=" or value name), can't this value be assigned a new value or changed?
Inside a lambda, captured variables are immutable by default. That doesn't depend on the captured variables or the way they were captured in any way. Rather, the function call operator of the closure type is declared const:
This function call operator or operator template is declared const
(9.3.1) if and only if the lambda-expression’s
parameter-declaration-clause is not followed by mutable.
Therefore, if you want to make the captured variables modifiable inside the body, just change the lambda to
auto add = [val] (int a) mutable -> int {
val += 2;
return a+val;
};
so the const-specifier is removed.
The operator () of a lambda is implicitly const unless the lambda is declared mutable - and you can't modify the data members in a const member function. This happens regardless of the type of the capture.
Just capture it by reference, it will work !!
auto add = [&val](int a) -> int{
//
}

Capturing pointers in lambda expression?

I have a function that uses a lambda expression.
std::vector<Bar*> mBars;
void foo(Bar* bar)
{
auto duplicateBars = std::remove_if(mBars.begin(), mBars.end(),
[bar] (const Bar* const &element)
{
return bar == element;
});
mBars.erase(duplicateBars, mBars.end());
}
Later, I reviewed the code and realized I could add two consts to foo's signature.
void foo(const Bar* const bar);
bar's pointer and data is now constant, but for the purpose of the lambda expression the pointer itself is constant, because I captured by value. However, the data pointed to can be changed and there is no way to change this, because const is not allowed in the lambda capture.
This is unintuitive to me. Is my interpretation correct? I can use the second signature, but I cannot protect the data from being changed in the lambda expression.
However, the data pointed to can be changed and there is no way to change this, because const is not allowed in the lambda capture.
No, when capturing by value in a lambda expression constness is preserved, i.e. capturing a pointer to const data will prevent changes to the data inside the lambda.
int i = 1;
const int* ptr = &i;
auto func = [ptr] {
++*ptr; // ERROR, ptr is pointer to const data.
}
A lambda will also add top-level constness to pointers when capturing by value (unless using mutable).
auto func = [ptr] {
ptr = nullptr; // ERROR, ptr is const pointer (const int* const).
}
auto func = [ptr] () mutable { // Mutable, will not add top-level const.
ptr = nullptr; // OK
}
I can use the second signature, but I cannot protect the data from being changed in the lambda expression.
You can protect the data from being changed inside the lambda by using const.
const Bar* bar = &bar_data;
auto b = [bar] (const Bar* element) { // Data pointed to by bar is read-only.
return bar == element;
};
Also the lambda expression takes a parameter of type const Bar* const &, i.e. reference to const pointer to const data. No need to take a reference, simply take a const Bar*.
More info about pointers and const: What is the difference between const int*, const int * const, and int const *?
Your question seems to arise from a misunderstanding of how capturing of variables in lambda expressions works. When you capture a variable by copy, the corresponding data member created in the closure type generated from the lambda expression will have the same type as the original object. This preserves const-ness, and you cannot go modify whatever bar points to within the body of the lambda.
From §5.1.2/15 [expr.prim.lambda]
An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that is not of the form & identifier or & identifier initializer. For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding
captured entity if the entity is not a reference to an object, or the referenced type otherwise.
Live demo