Return 转发参考参数 - 最佳实践

Return forwarding reference parameters - Best practice

在以下场景中

template <class T>
? f(T&& a, T&& b)
{
    return a > b ? a : b; 
}

最佳 return 类型是什么?到目前为止我的想法是:

  1. Return 通过 r 值引用,完美转发函数参数:

    template <class T>
    decltype(auto) f(T&& a, T&& b)
    {
        return a > b ? forward<T>(a) : forward<T>(b); 
    }
    
  2. 移动构建 return 值:

    template <class T>
    auto f(T&& a, T&& b)
    {
        return a > b ? forward<T>(a) : forward<T>(b); 
    }
    
  3. 尝试找到启用 (N)RVO 的方法(尽管我认为因为我想使用不可能的函数参数)

这些解决方案有问题吗?有更好的吗?最佳做法是什么?

这取决于你的意图和你T的移动意识。每个案例都是有效的,但在行为上存在差异;这:

template <class T>
decltype(auto) f(T&& a, T&& b)
{
    return a > b ? forward<T>(a) : forward<T>(b); 
}

将完美转发左值或右值引用,导致根本没有临时对象。如果你把 inline 放在那里(或者编译器为你放​​它)就像把参数放在适当的位置。针对 "first sight intuition" 它不会由于 'referencing' 死堆栈对象(对于右值情况)而产生任何运行时错误,因为我们转发的任何临时对象都来自我们上方(或我们下方,取决于您在函数调用期间如何看待堆栈)。另一方面,这(在右值的情况下):

template <class T>
auto f(T&& a, T&& b)
{
    return a > b ? forward<T>(a) : forward<T>(b); 
}

将移动构造一个值(auto 永远不会推导出 &&&)并且如果 T 不可移动构造,那么您将调用复制构造函数(如果 T 是左值引用,将始终进行复制)。如果您在表达式中使用 f,您毕竟会产生一个 xvalue(这让我想知道编译器是否可以使用初始右值,但我不会打赌)。

长话短说,如果使用 f 的代码是以处理右值和左值引用的方式构建的,我会选择第一个,毕竟这是逻辑 std::forward 是围绕加上当你有左值引用时你不会产生任何副本。

对于选项 1,您需要使用两个模板参数以在两个参数具有不同的值类别时启用转发。应该是

template <typename T, typename U>
decltype(auto) f(T&& a, U&& b)
{
    return a > b ? std::forward<T>(a) : std::forward<U>(b);
}

这里 return 实际得到的东西很难算出来。首先你需要知道ab的值类别,以及TU的推导结果。然后,无论您选择什么配对,都会通过三元运算符的非常复杂的规则。最后,它的输出通过 decltype 规则为您提供函数的实际 return 类型。

我确实坐下来解决了一次,with a little help from SO。根据我的记忆,它是这样的:当且仅当 ab 是兼容类型的左值引用时,结果将是一个可变的左值引用;如果 ab 之一是 const 左值引用而另一个是任何类型的引用,则结果将是 const 左值引用;否则 f 将 return 一个新值。

换句话说,它对任何给定的参数对都做正确的事情——可能是因为有人坐下来写下规则,这样它就会做正确的事情在所有情况下。

选项 2 与选项 1 完全相同,除了 decltype 规则不会发挥作用,函数将始终 return 一个新值 -- plain auto 永远不会推导出引用。

我想不出在 RVO 方面可行的选项 3,因为正如您所说,您正在使用参数。

总而言之,我认为 (1) 是您要找的答案。