我从http://www.cplusplus.com/reference/utility/forward获取了样本:
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a);
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
打印
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]
但是如果我想制作重载模板,那就是:
template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}
template<typename T>
void overloaded (T&& x) {std::cout << "[rvalue]";}
打印
calling fn with lvalue: [rvalue][rvalue]
calling fn with rvalue: [rvalue][rvalue]
有没有办法使用模板功能来扣除我转移到的功能?
答案 0 :(得分:5)
是的,只需在第二次重载中添加const
:
template<typename T>
void overloaded (const T& x);
template<typename T>
void overloaded (const T&& x);
// ^^^^^
您需要const
的原因是使x
不是转发引用。转发引用非常贪婪,如果你没有传入完全相同的类型(包括任何cv限定符),那么将选择转发引用重载。
在您的情况下,因为您没有将const
对象传递给overload
,所以第二次重载将始终是更好的匹配。
但是如果你在那里添加const
,那么它不再是转发参考,只能接受rvalues而不是左值,并且不能更好地匹配左值作为结果但是对于任何rvalues都比const&
重载更好。
如果您需要从x
移动,那么您将不得不做其他事情。移除const&
重载并在转发引用中分支,无论您是否有左值或左值:
template <typename T> void overloaded(T &&x) {
if /*constexpr*/ (std::is_lvalue_reference_v<T>)
std::cout << "[lvalue]";
else
std::cout << "[rvalue]";
}
注意:如果您执行的某些特定内容对分支或其他分支无效,则您需要使用if constexpr
。
答案 1 :(得分:5)
每当函数模板参数的类型为“对模板参数的rvalue引用”时,就像x
中的template<typename T> void overloaded(T&& x);
一样,它就变成了“转发引用”,即“通用引用”。它们遵循特殊规则,即它们可以匹配左值或右值参数。
当参数为右值时,T
的类型为非引用,参数类型为右值引用。
当参数是左值时,T
是左值引用类型。这是有意义的,因为第二个“参考折叠规则”:如果您向类型别名(&
添加&&
或typedef
,则使用using
定义类型,或类型模板参数)已经是一个引用,它形成一个引用类型,如果别名是一个右值引用并且你在其他三个案例中添加&&
或左值引用,则它是一个右值引用。
因此,当overloaded(x)
是x
类型的左值时,当您int
时,overloaded
的两个重载都匹配:
template<typename T>
void overloaded (const T& x);
// Specialization void overloaded<int>(const int&);
template<typename T>
void overloaded (T&& x);
// Specialization void overloaded<int&>(int&);
但第二个专业化是一个更好的匹配参数类型,所以它赢了。
为了防止这种情况,我会使用SFINAE技术将第二个overloaded
限制为仅非引用类型。
#include <type_traits>
// As before:
template<typename T>
void overloaded (const T& x);
template<typename T>
auto overloaded (T&& x) -> std::enable_if_t<!std::is_reference<T>::value>;
现在给出任何lvalue参数,第二个模板会将T
推导为左值引用类型,无法将其替换为返回类型,然后模板将被忽略以进行重载解析,以便第一个可以使用过载。
答案 2 :(得分:1)
第二个overloaded
模板不适用于rvalues,而是转发引用。接收调用往往非常积极,因为它会完全正确地推断出const-enss和value-ness,几乎在所有情况下都是首选(你的int调用是左值,但它不是const,所以这就是为什么它被调用)。
要使其工作,您需要约束该模板。当该模板与左值匹配时,T
将具有引用类型,以便引用折叠规则表明T&&
将折叠为T&
。所以我们可以这样做:
template<typename T>
void overloaded (const T& x) {std::cout << "[lvalue]";}
template<typename T, std::enable_if_t<!std::is_reference<T>::value, int> = 0>
void overloaded (T&& x) {std::cout << "[rvalue]";}
如果你这样做,那么你应该得到预期的输出。