如何正确使用通过转发引用传递的可调用对象?

How do correctly use a callable passed through forwarding reference?

我习惯于将 lambda 函数(和其他可调用对象)传递给模板函数——并使用它们——如下所示

template <typename F>
auto foo (F && f)
 {
   // ...

   auto x = std::forward<F>(f)(/* some arguments */);

   // ... 
 }

我的意思是:我习惯通过转发引用传递它们并调用它们通过 std::forward.

另一位 Stack Overflow 用户争辩说(请参阅对 的评论),调用函数两次或更多次是危险的,因为它在语义上无效并且有潜在危险(并且可能还有未定义的行为)当函数使用 r 值引用调用。

我部分地误解了他的意思(我的错)但剩下的疑问是下面的 bar() 函数(在同一个对象上有一个不容置疑的倍数 std::forward )它是正确的代码还是这(也许只是潜在的)危险。

template <typename F>
auto bar (F && f)
 {
   using A = typename decltype(std::function{std::forward<F>(f)})::result_type;

   std::vector<A>  vect;

   for ( auto i { 0u }; i < 10u ; ++i )
      vect.push_back(std::forward<F>(f)());

   return vect;
 }

前进只是有条件的举动。

因此,多次转发同一个东西,一般来说,和多次离开它一样危险。

未评估的前锋不会移动任何东西,所以这些不算数。

通过 std::function 的路由增加了一个问题:该推导仅适用于函数指针和具有单个函数调用运算符的函数对象,该运算符不是 && 合格的。对于这些,右值和左值调用如果都编译则总是等价的。

如果您只是将可调用对象转发到另一个地方,或者只调用一次可调用对象,我认为通常使用 std::forward 是正确的做法。正如 所解释的那样,这将在一定程度上保留可调用对象的值类别,并允许调用可能重载的函数调用运算符的 "correct" 版本。

原始线程中的问题是可调用对象在循环中被调用,因此可能被调用不止一次。来自另一个线程的具体示例是

template <typename F>
auto map(F&& f) const
{
    using output_element_type = decltype(f(std::declval<T>()));

    auto sequence = std::make_unique<Sequence<output_element_type>>();

    for (const T& element : *this)
        sequence->push(f(element));

    return sequence;
}

在这里,我认为调用 std::forward<F>(f)(element) 而不是 f(element),即

template <typename F>
auto map(F&& f) const
{
    using output_element_type = decltype(std::forward<F>(f)(std::declval<T>()));

    auto sequence = std::make_unique<Sequence<output_element_type>>();

    for (const T& element : *this)
        sequence->push(std::forward<F>(f)(element));

    return sequence;
}

可能会有问题。就我的理解而言,右值的定义特征是不能明确引用它。特别是,自然没有办法在一个表达式中多次使用同一个纯右值(至少我想不出一个)。此外,据我所知,如果您使用 std::movestd::forward 或任何其他方式获取 xvalue,即使在同一个原始对象上,结果每次都会是一个新的 xvalue .因此,也不可能有一种方法可以多次引用同一个 xvalue。由于同一个右值不能多次使用,我认为(另请参阅 下方的评论)对于重载函数调用运算符来说,通常可以有效地执行一些只能执行一次的操作,以防万一调用发生在右值上,例如:

class MyFancyCallable
{
public:
    void operator () & { /* do some stuff */ }
    void operator () && { /* do some stuff in a special way that can only be done once */ }
};

MyFancyCallable 的实现可能假设选择 && 合格版本的调用不可能发生多次(在给定对象上)。因此,我会考虑将同一个可调用项转发到多个调用中以在语义上被破坏。

当然,从技术上讲,对于转发或移动对象的实际含义没有统一的定义。最后,真正取决于所涉及的特定类型的实现来为其分配含义。因此,您可以简单地指定作为接口的一部分,传递给您的算法的潜在可调用对象必须能够处理在引用同一对象的右值上被多次调用的问题。然而,这样做几乎违背了 C++ 中通常如何使用右值引用机制的所有约定,而且我真的不明白这样做可能会获得什么……

我想说一般规则适用于这种情况。在变量 moved/forwarded 之后你不应该对它做任何事情,除了可能分配给它。

因此...

How do correctly use a callable passed through forwarding reference?

仅在您确定不会再次呼叫时转发(即在最后一次呼叫时,如果有的话)。

如果它从未被多次调用,则没有理由不转发。


至于为什么您的代码段可能是危险的,请考虑以下仿函数:

template <typename T>
struct foo
{
    T value;
    const T &operator()() const & {return value;}
    T &&operator()() && {return std::move(value);}
};

作为优化,operator() 在右值上调用时允许调用者从 value.

移动

现在,如果给定此仿函数,您的模板将无法编译(因为,如 T.C 所说,在这种情况下,std::function 无法确定 return 类型).

但是如果我们稍微改变一下:

template <typename A, typename F>
auto bar (F && f)
 {    
   std::vector<A>  vect;

   for ( auto i { 0u }; i < 10u ; ++i )
      vect.push_back(std::forward<F>(f)());

   return vect;
 }

那么当给定这个仿函数时它会 break spectacularly