完美转发可通话对象

时间:2019-01-29 10:28:52

标签: c++ lambda c++14 perfect-forwarding

我想出了以下代码,将类似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();
}

我担心被引用捕获。

  1. 定义明确吗?
  2. 我是否保证callable永远不会被复制并且在生命周期结束后也不会使用?

这被标记为C ++ 14,但适用于以下所有标准。

3 个答案:

答案 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::applythe 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。