Lambda Capture as Const Reference

Lambda capture as const reference?

const isn't in the grammar for captures as of n3092:

capture:
identifier
& identifier
this

The text only mention capture-by-copy and capture-by-reference and doesn't mention any sort of const-ness.

Feels like an oversight to me, but I haven't followed the standardization process very closely.

value turned const in lambda capture?

When capturing by copy, all captured objects are implicitly const. You have to explicitly mark lambda as mutable to disable that:

auto bla = [s]() mutable {
s.reset();
};

Also, if you want to reset actual s and not a copy, you want to capture by reference. You don't need mutable when capturing by reference, in this case constness is inferred from the actual object:

auto bla = [&s]() {
s.reset();
};

Should we capture by const reference in lambda?

I've been wondering about this myself.

Since the operator() is const by default, I would assume that allowing const references is acceptable as well.

With the current standards(C++17), the closest I got to this behaviour is:

auto c = [ &x = std::as_const(x) ] () { std::cout << x << std::endl; };

Workaround in C++11/C++14 would be(thanks to Daniel for suggestion):

auto const & crx = x;
auto c = [ &crx ] () { std::cout << crx << std::endl; };

Capturing by const reference in mutable lambda

Use std::as_const:

#include <future>
#include <utility>
#include <vector>

int main() {
std::promise<int> p;
const int N = 2;
std::vector<int> v = {1,2,3};
auto foo = [&N, &v = std::as_const(v), p = std::move(p)]() mutable {
// v.push_back(4); // does not compile
p.set_value(v[N]);
};
}

Passing by constant reference in the lambda capture list

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'
};

What will happen if I pass a mutable lambda to a function as const reference?

In first case both calls of call(f1) are using same instance of std::function<void()>.

In second case call(f2); implicit conversion form lambda to std::function<void()> kicks in and respective temporary object is created. So second call uses new copy of temporary object.

Try transform this code in cppinsights to see this with more details.

int main()
{

class __lambda_10_32
{
public:
inline /*constexpr */ void operator()()
{
std::cout.operator<<(++a).operator<<(std::endl);
}

private:
int a;
public:
// inline /*constexpr */ __lambda_10_32(const __lambda_10_32 &) noexcept = default;
// inline /*constexpr */ __lambda_10_32(__lambda_10_32 &&) noexcept = default;
__lambda_10_32(const int & _a)
: a{_a}
{}

};

std::function<void ()> f1 = std::function<void ()>(__lambda_10_32{0});
call(f1);
call(f1);

class __lambda_16_15
{
public:
inline /*constexpr */ void operator()()
{
std::cout.operator<<(++a).operator<<(std::endl);
}

private:
int a;
public:
// inline /*constexpr */ __lambda_16_15(const __lambda_16_15 &) noexcept = default;
// inline /*constexpr */ __lambda_16_15(__lambda_16_15 &&) noexcept = default;
__lambda_16_15(const int & _a)
: a{_a}
{}

};

__lambda_16_15 f2 = __lambda_16_15{0};
call(std::function<void ()>(__lambda_16_15(f2)));
call(std::function<void ()>(__lambda_16_15(f2)));
return 0;
}

Binding a const function reference to a lambda

As mentioned already, a capture-less lambda is convertible to a function pointer. So if you want to bind that static function to a reference, you need to dereference the pointer.

int(&foo)(int, int) = *[](int a, int b) { return a + b; };

Applying * to the lambda causes a bunch of machinery to kick in. Since the lambda doesn't overload operator*, but does implement a conversion to a pointer type, that conversion happens. Afterwards * is applied to the returned pointer and that yields a function lvalue. That lvalue can then bind to the reference.

Here it is live.

Why do fields in non-mutable lambdas use const when capturing const values or const references?

Did I miss something important, or has it simply been standardised this way to make the standard more simpler somehow?

The original lambda proposal,

  • N2550: Lambda Expressions and Closures:
    Wording for Monomorphic Lambdas (Revision 4)

differentiated between the captured object's type and the type of the corresponding data member of the lambda's closure type:

/6 The type of the closure object is a class with a unique name, call it F, considered to be defined at the point where the
lambda expression occurs.

Each name N in the effective capture set is looked up in the context
where the lambda expression appears to determine its object type;
in the case of a reference, the object type is the type to which the reference refers. For each element in the effective capture set, F
has a private non-static data member as follows:

  • if the element is this, the data member has some unique name, call it t, and is of the type of this ([class.this],
    9.3.2);
  • if the element is of the form & N, the data member has the name N and type “reference to object type of N”;
    5.19. CONSTANT EXPRESSIONS 3
  • otherwise, the element is of the form N, the data member has the name N and type “cv-unqualified object type of N”.

In this original wording OP's examples would not result in a const-qualified data member v. We may also note that we recognize the wording

in the case of a reference, the object type is the type to which the reference refers

which is present (but directly stating the type of the data member as opposed to the object type) in [expr.prim.lambda.capture]/10 of the (most recent draft of) the eventual wording on lambdas:

The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entity otherwise.

What happened was

  • N2927: New wording for C++0x Lambdas (rev. 2)

which re-wrote a large part of the wording from N2550:

During the meeting of March 2009 in Summit, a large number of issues relating to C++0x
Lambdas were raised and reviewed by the core working group (CWG). After deciding on a clear
direction for most of these issues, CWG concluded that it was preferable to rewrite the section
on Lambdas to implement that direction. This paper presents this rewrite.

particularly, for the context of this question, resolving CWG issue

  • CWG 756. Dropping cv-qualification on members of closure objects

[...] Consider the following example:

void f() {
int const N = 10;
[=]() mutable { N = 30; } // Okay: this->N has type int, not int const.
N = 20; // Error.
}

That is, the N that is a member of the closure object is not const,
even though the captured variable is const. This seems strange, as
capturing is basically a means of capturing the local environment in a
way that avoids lifetime issues.
More seriously, the change of type
means that the results of decltype, overload resolution, and template
argument deduction applied to a captured variable inside a lambda
expression can be different from those in the scope containing the
lambda expression, which could be a subtle source of bugs.

after which the wording (as of N2927) was made into the one we saw eventually go into C++11

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.

Were I dare to speculate, the resolution of CWG 756 also meant keeping cv-qualifiers for value-captures of entities that were of reference types, which may arguably have been an oversight.



Related Topics



Leave a reply



Submit