完美转发可调用的

YSC*_*YSC 16 c++ lambda perfect-forwarding c++14

我想出了以下代码将R()alike转换为类似void()callable:

#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();
}
Run Code Online (Sandbox Code Playgroud)

我很担心被引用捕获.

  1. 它定义明确吗?
  2. 我保证callable永远不会复制,并且在其生命周期结束后从未使用过吗?

这是标记的C++ 14,但适用于所有以下标准.

Pas*_* By 13

Lambdas是带有a的匿名结构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
    };
}
Run Code Online (Sandbox Code Playgroud)


Max*_*kin 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());
    };
}
Run Code Online (Sandbox Code Playgroud)

如果callable是l值引用但是它的生命周期范围小于返回的lambda捕获的范围,则仍然可能面临生命周期问题discardable.因此,始终移动或复制可能是最安全和最容易的callable.

作为旁注,尽管有新的专用实用程序可以完美地转发函数对象的值类别std::apply,但标准库算法总是通过按值接受它们来复制函数对象.因此,如果一个重载operator()()&,operator()()&&标准库将始终使用operator()()&.

  • @LightnessRacesinOrbit有人可能会声明`operator()()&&`和`operator()()&`.国际海事组织,这是脆弱的,但可以想象.虽然标准库只是复制功能对象而不关心这些琐事. (2认同)
  • *"标准库总是通过按值接受它们来复制函数对象"*.这就是STL**在转发引用时不存在的**.[std :: apply](https://en.cppreference.com/w/cpp/utility/apply)使用转发引用作为反例. (2认同)

Jar*_*d42 5

当您使用捕获的lambda的悬空参考时,您的程序是UB.

因此,为了完善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)(); 
    };
}
Run Code Online (Sandbox Code Playgroud)

它移动构造临时lambda.