在以下场景中
template <class T>
? f(T&& a, T&& b)
{
return a > b ? a : b;
}
最佳回报类型是什么?到目前为止我的想法是:
返回r值refs,完美转发函数参数:
template <class T>
decltype(auto) f(T&& a, T&& b)
{
return a > b ? forward<T>(a) : forward<T>(b);
}
移动构造返回值:
template <class T>
auto f(T&& a, T&& b)
{
return a > b ? forward<T>(a) : forward<T>(b);
}
尝试寻找启用(N)RVO的方法(即使我认为因为我想使用那些不可能的函数参数)
这些解决方案有问题吗?还有更好的吗?什么是最佳做法?
答案 0 :(得分:5)
对于选项1,您需要使用两个模板参数,以便在两个参数具有不同的值类别时启用转发。它应该是
template <typename T, typename U>
decltype(auto) f(T&& a, U&& b)
{
return a > b ? std::forward<T>(a) : std::forward<U>(b);
}
实际返回的内容非常棘手。首先,您需要了解a
和b
的值类别,以及推导出T
和U
的值。然后,无论您选择哪种配对,都要经历三元运算符的非常复杂的规则。最后,它的输出通过decltype
规则来为您提供函数的实际返回类型。
我确实坐了下来,一次又一次地工作,with a little help from SO。在我的记忆中,它是这样的:当且仅当a
和b
是兼容类型的左值引用时,结果才是可变的左值引用;如果a
和b
之一是const左值引用而另一个是任何类型的引用,则结果将是const左值引用;否则f
将返回一个新值。
换句话说,它对任何给定的参数都做了正确的事情 - 可能是因为有人坐下来准确地编写规则,以便 在所有情况下做正确的事情。< / p>
选项2与选项1完全相同,只是decltype
规则没有进入,并且该函数将始终返回一个新值 - plain auto
从不推断参考。
我想不出一个可以在RVO方面工作的选项3,因为你说你正在使用这些参数。
总而言之,我认为(1)是您正在寻找的答案。
答案 1 :(得分:2)
这取决于您的意图和T
的移动意识。每个案例都有效,但行为有差异;这个:
template <class T>
decltype(auto) f(T&& a, T&& b)
{
return a > b ? forward<T>(a) : forward<T>(b);
}
将完美地转发左值或右值参考值,从而完全没有临时值。如果你在那里放一个inline
(或者编译器给你放的话)就好像有参数一样。对于“第一视觉直觉”,由于“引用”死堆栈对象(对于右值情况),它不会产生任何运行时错误,因为我们转发的任何临时来自我们上方的呼叫者(或低于我们,取决于你如何在函数调用期间考虑堆栈)。另一方面,这(在右边的情况下):
template <class T>
auto f(T&& a, T&& b)
{
return a > b ? forward<T>(a) : forward<T>(b);
}
将移动 - 构造一个值(auto
永远不会推导出&
或&&
),如果T
不能移动构造,那么您将调用一个副本构造函数(如果T
是左值引用,则始终生成副本)。如果你在一个表达式中使用f
,你毕竟会生成一个xvalue(这让我想知道编译器是否可以使用初始rvalue,但我不打赌它)。
长话短说,如果使用f
的代码以一种处理rvalue和lvalue引用的方式构建,我会选择第一个选择,毕竟逻辑std::forward
是如果你有左值参考,你就不会产生任何副本。