Lambda Capture by Value Mutable Doesn't Work with Const &

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

[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?

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.

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 can't a const mutable lambda with an auto& parameter be invoked?

There are two interesting things here.

First, a lambda's call operator (template) is const by default. If you provide mutable, then it is not const. The effect of mutable on a lambda is solely the opposite of the effect of trailing const in normal member functions (it does not affect lambda capture, etc.)

So if you look at this:

auto const f3 = [](auto&) mutable {};
static_assert(std::is_invocable_v<decltype(f3), int&>); // failed

This is a const object, whose call operator template (because it's a generic lambda) is non-const. So you can't invoke it, for the same reason you can't invoke a non-const member function on a const object in any other context. See this other answer.

Second, it has been pointed out that, nevertheless, this works:

auto const f4 = [](int&) mutable {}; // changed auto& to int&
static_assert(std::is_invocable_v<decltype(f4), int&>); // now ok

This is not a compiler bug. Nor does it mean that what I just said was wrong. f4 still has a non-const call operator. Which you cannot invoke, because f4 is a const object.

However.

There's one other interesting aspect of lambdas that have no capture: they have a conversion function to a function pointer type. That is, we usually think about the lambda f4 as looking like this:

struct __unique_f4 {
auto operator()(int&) /* not const */ { }
};

And, if that were the whole story, const __unique_f4 is indeed not invocable with int&. But it actually looks like this:

struct __unique_f4 {
auto operator()(int&) /* not const */ { }

// conversion function to the appropriate function
// pointer type
operator void(*)(int&)() const { /* ... */ }
};

And there is this rule we have where when you invoke an object, like f(x), you not only consider f's call operators -- those members named operator() -- but you also consider any of f's surrogate call functions -- are there any function pointers that you can convert f to, to then invoke.

In this case, you can! You can convert f4 to a void(*)(int&) and that function pointer is invocable with int&.

But that still means that f4's call operator is not const, because you declared it mutable. And it doesn't say anything about whether you can have mutable lambdas take reference parameters.

C++0x lambda capture by value always const?

Use mutable.


auto bar = [=] () mutable -> bool ....

Without mutable you are declaring the operator () of the lambda object const.

Can `this` be changed in a mutable lambda?

Init-captures of this

(void) [p = this]() mutable { 
p = nullptr; //#1: ok everywhere
(void)p;
};

This uses init-capture to capture the this pointer by value, as per [expr.prim.lambda.capture]/6, meanings it's a copy of the this pointer. In contexts where this is const-qualified, the copy can naturally not be used to changed this (even if the lambda is mutable; compare with 'point to const'), but as the lambda is mutable, the pointer (copy) can be used to point to something different, e.g. nullptr.

struct S {
void f() const {
(void) [p = this]() mutable {
p->value++; // ill-formed: 'this' is pointer to const, meaning
// 'p' is pointer to const.
p = nullptr; // OK: 'p' is not const pointer
(void)p;
};
}

void f() {
(void) [p = this]() mutable {
p->value++; // OK: 'this' is pointer to non-const, meaning
// 'p' is pointer to non-const.
p = nullptr; // OK: 'p' is not const pointer
(void)p;
};
}
int value{};
};


Simple-captures of this:

(void) [this]() mutable { 
this = nullptr; //#2: ok in MSVC only
};

As per [expr.prim.lambda.capture], ignoring the case of capture-default:s:

  • A capture-list contains a capture
  • A capture is either a simple-capture or an init-capture; we ignore the latter case as it was covered above
  • A simply-capture has one of the following forms:
    • identifier ...opt
    • &identifier ...opt
    • this
    • *this

As per [expr.prim.lambda.capture]/10 [emphasis mine]:

An entity is captured by copy if

  • (10.1) it is implicitly captured, the capture-default is =, and the captured entity is not *this, or

  • (10.2) it is explicitly captured with a capture that is not of the form this, & identifier, or & identifier initializer.

Only the simple-capture form *this allows explicitly capturing the *this object by copy. The simple capture this, however, captures the *this object(+) by reference, as per [expr.prim.lambda.capture]/12:

(+) The simple-capture:s this and *this both denote the local entity *this, as per [expr.prim.lambda.capture]/4.

An entity is captured by reference if it is implicitly or
explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the
closure type for entities captured by reference. [...]

Thus:

struct S {
void f() const {
(void) [this]() mutable {
// '*this' explicitly-captured by-reference
this->value++; // ill-formed: 'this' is pointer to const
this = nullptr; // #2 ill-formed: 'this' is not a modifyable lvalue
};
}

void f() {
(void) [this]() mutable {
// '*this' explicitly-captured by-reference
this->value++; // OK: 'this' is pointer to non-const
this = nullptr; // #2 ill-formed: 'this' is not a modifyable lvalue
};
}
int value{};
};

As per [class.this]/1, this is not a modifyable lvalue, which is why #2 is ill-formed:

In the body of a non-static ([class.mfct]) member function, the keyword this is a prvalue whose value is a pointer to the object for which the function is called. The type of this in a member function whose type has a cv-qualifier-seq cv and whose class is X is “pointer to cv X”. [...]

Which, as per [expr.prim.lambda.closure]/12, applies also to when this is used in lambdas:

The lambda-expression's compound-statement yields the function-body ([dcl.fct.def]) of the function call operator, but for purposes of name lookup, determining the type and value of this and transforming id-expressions referring to non-static class members into class member access expressions using (*this) ([class.mfct.non-static]), the compound-statement is considered in the context of the lambda-expression.

MSVC is this incorrect to accept your snippet (accepts-invalid).

And indeed, in the following example (demo):

#include <iostream>

struct S;

void g(S *& ) { std::cout << "lvalue pointer to S"; }
void g(S *&& ) { std::cout << "rvalue pointer to S"; }

struct S {
void f() {
auto l = [this]() { g(this); };
l();
}
};

int main() {
S s{};
s.f();
}

We expect the second g overload to be a better match, as this is a prvalue. However, whilst GCC and Clang behaves as expected:

// GCC & Clang: rvalue pointer to S

MSVC fails to even compile the program:

// MSVC: error, no viable overload; arg list is '(S *const )'

Which is in violation of [class.this]/1, as:

[...] The type of this in a member function whose type has a cv-qualifier-seq cv and whose class is X is “pointer to cv X” [...]

... and not "const pointer to cv X" (constness on a prvalue would be weird, to begin with).

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.

Lambda capture-by-value while transfering ownership

A lambda is not a std::function.

You are right that c++14 allows moving something into a lambda, and even move said lambda afterwards.

A std::function on the other hand requires the callable to be Copy Constructable and Copy Assignable.

From cppreference

Class template std::function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy, and invoke any CopyConstructible Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.

The stored callable object is called the target of std::function. If a std::function contains no target, it is called empty. Invoking the target of an empty std::function results in std::bad_function_call exception being thrown.

std::function satisfies the requirements of CopyConstructible and CopyAssignable.

Some ideas for a possible solution could be:

  • Create your own function wrapper (or use a library) that supports move-only callables.

  • Wrap you std::unique_pre in a std::shared_ptr and capture that.

The first option seems much more robust, but just to show the idea for a quick dirty work-around.

#include <memory>
#include <functional>

int main() {
auto moveonly = std::make_unique<int>(5);
auto wrapped = std::make_shared<std::unique_ptr<int>>(std::move(moveonly));

auto lam = [wrapped]() {
return *(*wrapped);
};

std::function<int()> f = lam;
}

Capturing a lambda in another lambda can violate const qualifiers

Does this mean that if I have a library function that accepts an arbitrary function argument and captures it in a lambda, I always need to make that lambda mutable, because I don't know what users can pass in?

That's a design decision for your library API. You can require client code to pass function objects with a const-qualified operator() (which is the case for non-mutable lambda expressions). If something different is passed, a compiler error is triggered. But if the context might require a function object argument that modifies its state, then yes, you have to make the internal lambda mutable.

An alternative would be to dispatch on the ability to invoke operator() on a const-qualified instance of the given function type. Something along those lines (note that this needs a fix for function objects with both const and non-const operator(), which results in an ambiguity):

template <class Fct>
auto wrap(Fct&& f) -> decltype(f(), void())
{
[fct = std::forward<Fct>(f)]() mutable { fct(); }();
}

template <class Fct>
auto wrap(Fct&& f) -> decltype(std::declval<const Fct&>()(), void())
{
[fct = std::forward<Fct>(f)]() { fct(); }();
}

Notably, wrapping f1 in std::function seems to resolve this problem (how?).

This is a bug in std::function due to its type-erasure and copy semantics. It allows non-const-qualified operator() to be invoked, which can be verified with such a snippet:

const std::function<void()> f = [i = 0]() mutable { ++i; };

f(); // Shouldn't be possible, but unfortunately, it is

This is a known issue, it's worth checking out Titus Winter's complaint on this.



Related Topics



Leave a reply



Submit