Perfect forwarding of a callable
Your program is UB as you use dangling reference of the captured lambda.
So to perfect forward capture in lambda, you may use
template<class Callable>
auto discardable(Callable&& callable)
{
return [f = std::conditional_t<
std::is_lvalue_reference<Callable>::value,
std::reference_wrapper<std::remove_reference_t<Callable>>,
Callable>{std::forward<Callable>(callable)}]
{
std::forward<Callable>(f)();
};
}
It move-constructs the temporary lambda.
Lambdas are anonymous structs with an operator()
, the capture list is a fancy way of specifying the type of its members. Capturing by reference really is just what it sounds like: you have reference members. It isn't hard to see the reference dangles.
This is a case where you specifically don't want to perfectly forward: you have different semantics depending on whether the argument is a lvalue or rvalue reference.
template<class Callable>
auto discardable(Callable& callable)
{
return [&]() mutable { (void) callable(); };
}
template<class Callable>
auto discardable(Callable&& callable)
{
return [callable = std::forward<Callable>(callable)]() mutable { // move, don't copy
(void) std::move(callable)(); // If you want rvalue semantics
};
}
Since callable
can be an xvalue there is a chance that it gets destroyed before the lambda capture, hence leaving you with a dangling reference in the capture. To prevent that, if an argument is an r-value it needs to be copied.
A working example:
template<class Callable>
auto discardable(Callable&& callable) { // This one makes a copy of the temporary.
return [callable = std::move(callable)]() mutable {
static_cast<void>(static_cast<Callable&&>(callable)());
};
}
template<class Callable>
auto discardable(Callable& callable) {
return [&callable]() mutable {
static_cast<void>(callable());
};
}
You can still face lifetime issues if callable
is an l-value reference but its lifetime scope is smaller than that of the lambda capture returned by discardable
. So, it may be the safest and easiest to always move or copy callable
.
As a side note, although there are new specialised utilities that perfect-forward the value category of the function object, like std::apply
, the standard library algorithms always copy function objects by accepting them by value. So that if one overloaded both operator()()&
and operator()()&&
the standard library would always use operator()()&
.