C++ 返回转发参考参数-最佳实践

C++ 返回转发参考参数-最佳实践,c++,c++11,c++14,move-semantics,rvalue-reference,C++,C++11,C++14,Move Semantics,Rvalue Reference,在下面的场景中 template <class T> ? f(T&& a, T&& b) { return a > b ? a : b; } 模板 ? f(T&a、T&b) { 返回a>b?a:b; } 最佳回报类型是什么?到目前为止,我的想法是: 通过r值引用返回,完美地转发函数参数: template <class T> decltype(auto) f(T&& a, T&& b) {

在下面的场景中

template <class T>
? f(T&& a, T&& b)
{
    return a > b ? a : b; 
}
模板
? f(T&a、T&b)
{
返回a>b?a:b;
}
最佳回报类型是什么?到目前为止,我的想法是:

  • 通过r值引用返回,完美地转发函数参数:

    template <class T>
    decltype(auto) f(T&& a, T&& b)
    {
        return a > b ? forward<T>(a) : forward<T>(b); 
    }
    
    模板
    decltype(自动)f(T&a、T&b)
    {
    返回a>b?向前(a):向前(b);
    }
    
  • 移动并构造返回值:

    template <class T>
    auto f(T&& a, T&& b)
    {
        return a > b ? forward<T>(a) : forward<T>(b); 
    }
    
    模板
    自动f(T&a、T&b)
    {
    返回a>b?向前(a):向前(b);
    }
    
  • 尝试找到一种启用(N)RVOs的方法(尽管我认为,因为我想使用函数参数,这是不可能的)


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

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

    template <class T>
    decltype(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);
    }
    
    模板
    数据类型(自动)f(T&a、U&b)
    {
    返回a>b?标准::转发(a):标准::转发(b);
    }
    
    这里实际返回的内容很难计算。首先,您需要知道
    a
    b
    的值类别,以及
    T
    U
    的推导结果。然后,无论你选择什么配对,都要经过三元运算符的非常复杂的规则。最后,它的输出经过
    decltype
    规则,为您提供函数的实际返回类型

    我确实曾经坐下来把这一切都解决过。据我记忆所及,它是这样的:当且仅当
    a
    b
    是兼容类型的左值引用时,结果将是可变的左值引用;如果
    a
    b
    中的一个是常量左值引用,而另一个是任何类型的引用,则结果将是常量左值引用;否则,
    f
    将返回一个新值

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

    选项2的操作与选项1完全相同,只是
    decltype
    规则不起作用,函数将始终返回一个新值--plain
    auto
    从不推断为引用

    我想不出一个选项3能在RVO方面起作用,因为正如你说的,你正在使用参数


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

    您如何期望有一个可以返回两种不同类型的返回类型?T还是U?@xaxxon说T是
    int
    U
    double
    。三元运算符的类型是什么?我会让你从那里捡起来,你拿到gist@xaxxon只要
    T
    可以转换为
    U
    (或相反,或根据标准定义的更复杂的内容),这是完全有效的。@Ajay因为
    字符a、b;decltype(a+b)c;std::cout
    模板decltype(自动)f(T&&a,U&&b)
    可以;或者,您可以将其设置为不可用,
    template auto f(T&&a,U&&b)->decltype(a>b?forward(a):forward(b))
    。在这两种情况下,编译器(遵循确定条件运算符的两个操作数的公共类型的规则)将找出适当的返回类型(如果有)。据我所知,最初的问题被编辑为删除
    U
    ,以避免混淆,远离SFINAE和类型推广事宜,专注于正确使用完美转发的实际主题,移动语义和右值引用。这回答了你的问题吗?(另一个答案引用了问题原始版本中的代码)不,我的意思是,在任何情况下,当值类别(和类型)不同于参数时,两次使用
    T
    都可能导致扣减失败expressions@PiotrSkotnicki尼科斯的假设是正确的。我不在乎有2、3或4种类型,我只是在问如何从函数中获取值,我应该前进还是移动来构造返回值?也许我应该编辑为只有一个参数?(除此之外,编辑后,
    f
    的签名是给定的,我不需要答案来改变这一点,我假设这个答案是“如果你有签名,那么……”@LorahAttkins这不仅仅是一个等价类型的问题,而且也是一个等价类别的问题。如果你想避免混淆,那么使用单参数函数将是一个更好的例子。我同意@PiotrSkotnicki的观点。我认为这个答案不是很有用,因为像这样的简单代码无法编译。
    template <typename T, typename U>
    decltype(auto) f(T&& a, U&& b)
    {
        return a > b ? std::forward<T>(a) : std::forward<U>(b);
    }