我正在观看Scott Meyer的视频“The Universal Reference / Overloading Collision Conundrum”,他举了一个不做的例子:
class MessedUp {
public:
template<typename T>
void doWork(const T& param) { std::cout << "doWork(const T& param)" << std::endl; }
template<typename T>
void doWork(T&& param) { std::cout << "doWork(T&& param)" << std::endl; }
};
.... //somewhere in the main
MessedUp m;
int w = 10;
const int cw = 20;
m.doWork(cw); // calls doWork(const T& param) as expected
m.doWork(std::move(cw)); // Calls doWork(T&& param)
我很好奇为什么编译器在模板重载解析期间选择了doWork(T&& param)
而不是doWork(const T& param)
。据我所知,const
对象无法移动。
答案 0 :(得分:4)
&&
并不意味着移动,它意味着右值参考。 rvalue引用只会绑定到临时(匿名)对象,或者被std::move
或std::forward
等函数强制转换为临时对象的对象,或者编译器自动标记为临时对象的对象,如同从在简单的return X;
行上运行。
您可以对const
对象进行右值引用。发生这种情况时,您无法移动(除非可以移动mutable
状态),但它仍然是右值参考。
现在,如果T&&
是左值引用,T
可以绑定到左值引用,因此在类型推导中,上下文T&&
可以称为通用引用。因此,上述设计的一个真正问题是m.doWork(w)
也会T&&
与T=int&
绑定。
在重载决策中,如果其他条件相同,那么template<typename T> void foo(T&&)
与T=foo&
匹配的函数与template<typename T> void foo(T&)
的{{1}}相比会更差;但是在在您的情况下,没有T=foo
,T
是foo(T const&)
。
答案 1 :(得分:4)
模板类型扣除和替换后,两个重载变为:
//template<typename T>
void doWork(const int& param) { std::cout << "doWork(const T& param)" << std::endl; }
//template<typename T>
void doWork(const int&& param) { std::cout << "doWork(T&& param)" << std::endl; }
请注意第二次重载中的T
如何推断为const int
。
现在,正常的重载解析会发生什么:我们比较将参数表达式std::move(cw)
(类型为const int
的xvalue)转换为参数类型所需的隐式转换序列。两者都排名为Exact Matches,因此我们必须查看[over.ics.rank] / 3中的断路器并比较两个隐式转换序列S1
和S2
(这里的引用绑定是转换序列):
标准转换序列
S1
是比标准转换序列S2
更好的转换序列 [...]
S1
和S2
是引用绑定[...],S1
将右值引用绑定到右值,S2
绑定左值引用。
由于xvalue 是 rvalue(和glvalue),此点适用,并选择第二个重载。
答案 2 :(得分:1)
&&
可用于确定非右值的临时右值。所以,你可以安全地窃取资源。
使用std::move
时,它会将类型转换为右值,编译器将使用&&
重载。
答案 3 :(得分:1)
正在发生的事情是使用doWork(T&& param)
调用T = const int
,因为这是一个完美的匹配(而不是转换为左值)。
如果您已经修改移动对象,它确实会失败,因为const对象无法移动。