Capturing pointers in lambda expression? - c++

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

Related

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).

When moving a unique_ptr into a lambda, why is it not possible to call reset?

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();
};
}

Why non-mutable lambda captures can be moved?

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();}
};

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

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.

Where are lambda captured variables stored?

How is it possible that this example works? It prints 6:
#include <iostream>
#include <functional>
using namespace std;
void scopeIt(std::function<int()> &fun) {
int val = 6;
fun = [=](){return val;}; //<-- this
}
int main() {
std::function<int()> fun;
scopeIt(fun);
cout << fun();
return 0;
}
Where is the value 6 stored after scopeIt is done being called? If I replace the [=] with a [&], it prints 0 instead of 6.
It is stored within the closure, which - in your code - is then stored within std::function<int()> &fun.
A lambda generates what's equivalent to an instance of a compiler generated class.
This code:
[=](){return val;}
Generates what's effectively equivalent to this... this would be the "closure":
struct UNNAMED_TYPE
{
UNNAMED_TYPE(int val) : val(val) {}
const int val;
// Above, your [=] "equals/copy" syntax means "find what variables
// are needed by the lambda and copy them into this object"
int operator() () const { return val; }
// Above, here is the code you provided
} (val);
// ^^^ note that this DECLARED type is being INSTANTIATED (constructed) too!!
Lambdas in C++ are really just "anonymous" struct functors. So when you write this:
int val = 6;
fun = [=](){return val;};
What the compiler is translating that into is this:
int val = 6;
struct __anonymous_struct_line_8 {
int val;
__anonymous_struct_line_8(int v) : val(v) {}
int operator() () const {
return val; // returns this->val
}
};
fun = __anonymous_struct_line_8(val);
Then, std::function stores that functor via type erasure.
When you use [&] instead of [=], it changes the struct to:
struct __anonymous_struct_line_8 {
int& val; // Notice this is a reference now!
...
So now the object stores a reference to the function's val object, which becomes a dangling (invalid) reference after the function exits (and you get undefined behavior).
The so-called closure type (which is the class type of the lambda expression) has members for each captured entity. Those members are objects for capture by value, and references for capture by reference. They are initialized with the captured entities and live independently within the closure object (the particular object of closure type that this lambda designates).
The unnamed member that corresponds to the value capture of val is initialized with val and accessed from the inside of the closure types operator(), which is fine. The closure object may easily have been copied or moved multiple times until that happens, and that's fine too - closure types have implicitly defined move and copy constructors just as normal classes do.
However, when capturing by reference, the lvalue-to-rvalue conversion that is implicitly performed when calling fun in main induces undefined behavior as the object which the reference member referred to has already been destroyed - i.e. we are using a dangling reference.
The value of a lambda expression is an object of class type, and
For each entity
captured by copy, an unnamed non-static data member is declared in the closure type.
([expr.prim.lambda]/14 in C++11)
That is, the object created by the lambda
[=](){return val;}
actually contains a non-static member of int type, whose value is 6, and this object is copied into the std::function object.