我想出了以下代码,将类似R()
的对象转换为类似void()
的可调用对象:
#include <utility>
template<class Callable>
auto discardable(Callable&& callable)
{ return [&]() { (void) std::forward<Callable>(callable)(); }; }
// ^-- is it ok?
int main()
{
auto f = discardable([n=42]() mutable { return n--; });
f();
}
我担心被引用捕获。
callable
永远不会被复制并且在生命周期结束后也不会使用? 这被标记为C ++ 14,但适用于以下所有标准。
答案 0 :(得分:13)
Lambda是带有operator()
的匿名结构,捕获列表是一种指定其成员类型的理想方法。听起来确实像是通过引用捕获:您有引用成员。不难看出参考悬挂物。
在这种情况下,您特别不想想要完全转发:根据参数是左值引用还是右值引用,您会有不同的语义。
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
};
}
答案 1 :(得分:7)
由于callable
可以是xvalue,因此有可能在lambda捕获之前被销毁,因此在捕获中会留下悬挂的引用。为了避免这种情况,如果参数是r值,则需要将其复制。
一个工作示例:
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());
};
}
如果callable
是左值引用,但其生存期范围小于discardable
返回的lambda捕获的生存期,则仍然会遇到生存期问题。因此,始终移动或复制callable
可能是最安全,最容易的方法。
作为旁注,尽管有一些新的专用实用程序可以完善转发功能对象的值类别,例如std::apply
,the standard library algorithms总是通过按值接受功能对象来复制它们。因此,如果一个人同时重载operator()()&
和operator()()&&
,则标准库将始终使用operator()()&
。
答案 2 :(得分:5)
当您使用捕获的lambda的悬空引用时,您的程序就是UB。
对于perfect forward capture in lambda,您可以使用
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)();
};
}
它会移动构造临时lambda。