AFAIK non-mutable lambdas capture variables as const. This makes me wonder why can they still be moved?
auto p = std::make_unique<int>(0);
auto f = [p = std::move(p)](){ p->reset(); }; // Error, p is const
auto f2 = std::move(f); // OK, the pointer stored inside lambda is moved
AFAIK non-mutable lambdas capture variables as const.
No, they do not. Their operator() overloads are const. The actual member variables aren't.
It's no different from:
class A
{
unique_ptr<int> p
public:
//Insert constructors here.
void operator() const {p->reset();}
};
Related
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).
When moving std::unique_ptr into the lambda, it is not possible to call reset() on it, because it seems to be const then:
error C2662: void std::unique_ptr<int,std::default_delete<_Ty>>::reset(int *) noexcept': cannot convert 'this' pointer from 'const std::unique_ptr<int,std::default_delete<_Ty>>' to 'std::unique_ptr<int,std::default_delete<_Ty>> &
#include <memory>
int main()
{
auto u = std::unique_ptr<int>();
auto l = [v = std::move(u)]{
v.reset(); // this doesn't compile
};
}
Why does this happen?
Is it possible to capture the std::unique_ptr in another way which allows calling reset() within the lambda (with C++17 or later)?
Why does this happen?
Because the function-call operator of a lambda,
Unless the keyword mutable was used in the lambda-expression, the function-call operator is const-qualified and the objects that were captured by copy are non-modifiable from inside this operator().
and
Is it possible to capture the std::unique_ptr in another way which allows to call reset() within the lambda
You need to mark it mutable.
mutable: allows body to modify the parameters captured by copy, and to call their non-const member functions
e.g.
auto l = [v = std::move(u)]() mutable {
v.reset();
};
Why does this happen?
Because lambdas are by default non-mutable. Therefore all captured objects are const. reset is a non-const member function that modifies the unique pointer.
Is it possible to capture the std::unique_ptr in another way which allows to call reset() within the lambda (with C++17 or later)?
Yes. Declare the lambda mutable:
[captures](arguments) mutable { body }
^^^^^^^
This is possible since C++11 where lambdas were introduced. All captured non-const objects of a mutable lambda are non-const copies.
To mutate a "member" of the lambda, you need the mutable keyword:
auto l = [v = std::move(u)] () mutable {
v.reset();
};
Within the lambda its data members are immutable by default. You need to append the mutable specifier to the lambda expression.
As an alternative, you could capture the unique_ptr by reference, as for example:
#include <memory>
int main()
{
auto u = std::unique_ptr<int>();
auto l = [&v = u]{
v.reset();
};
}
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.
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
I'd like to get my mucky paws on the operator() of a lambda function. The following seems up the task:
template <typename F>
void bar(F func) {
void (F ::*pm)();
pm = &F::operator();
}
However, in the following, I need to include the mutable keyword. Why is that? Is it possible to above instead declare a pointer to member function, which can target arbitrary lambdas?
int main(int argc, char *argv[])
{
bar([]() mutable {});
return 0;
}
According to 5.1.2 of the N3291 C++0x specification, the lambda's operator() is const unless you explicitly declare it mutable:
This function call operator is declared const (9.3.1) if and only if the lambda-
expression’s parameter-declaration-clause is not followed by mutable.
You may be able to do some template metaprogramming magic to detect which is which.
However, it should be noted that, once func goes out of scope, you can't use that member pointer anymore. And it is a member pointer, not a function pointer, so you can't convert between the two.
C++0x lambdas are const by default, unlike the rest of the language. Consequently, your member-function variable must be const as well. The following should work:
template<typename F>
void bar(F func) {
typedef void (F::*pm_t)() const;
pm_t pm = &F::operator();
}
Consider this:
int x=0;
auto show = [x]() mutable
{
x++;
};
show();
The variable 'x' is being captured by-value, and without mutable, you cannot modify the local copy of this variable. Specify mutable to enable modification of this local copy. For by-value captures, it doesn't matter if value is modified, the original x variable won't be modified.