#include <type_traits>
int main()
{
auto f1 = [](auto&) mutable {};
static_assert(std::is_invocable_v<decltype(f1), int&>); // ok
auto const f2 = [](auto&) {};
static_assert(std::is_invocable_v<decltype(f2), int&>); // ok
auto const f3 = [](auto&) mutable {};
static_assert(std::is_invocable_v<decltype(f3), int&>); // failed
}
See demo
Why can't a const mutable lambda take a reference argument?
CodePudding user response:
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.
CodePudding user response:
You get an error for this for the very same reason:
struct foo {
void operator()(){}
};
int main() {
const foo f;
f();
}
The error is:
<source>:7:5: error: no matching function for call to object of type 'const foo'
f();
^
<source>:2:10: note: candidate function not viable: 'this' argument has type 'const foo', but method is not marked const
void operator()(){}
^
Because you cannot call a non-const method on a const instance. Lambdas got the default constness right, so without mutable
the operator()
is const
. With mutable
the operator()
is a non-const method that you cannot call on a const f3;