为何左值会明确选择左值引用重载而非转发引用重载?
为什么左值调用
foo时会选中左值引用模板而非转发引用模板? 这个问题真的戳中了C++模板重载决议里的一个容易混淆的点!我当初刚接触转发引用的时候也踩过类似的坑,咱们一步步拆解清楚:
先回顾你的推导结论
你说得没错:
- 对于模板#1
template <class T> int foo(T& x),传入左值int i时,T被推导为int,特化出int foo(int& x)。 - 对于模板#2
template <class T> int foo(T&& x)(转发引用),传入左值int i时,T会被推导为int&,经过引用折叠(int& &&→int&),最终特化出的也是int foo(int& x)。
从特化后的签名来看,两个函数完全一样,按常理确实会觉得应该出现调用歧义,但实际并没有——这是因为重载决议的最后一步还有**模板偏序(Template Partial Ordering)**这个决胜规则!
模板偏序:判断哪个模板更"特化"
当两个函数模板特化后的签名完全相同时,C++会去比较原始模板的"特化程度",优先选择更特化的那个模板。
判断模板偏序的逻辑可以简化为:
我们尝试用其中一个模板的参数类型,去匹配另一个模板的参数模板,看是否能推导出有效结果。如果A模板的所有可能参数都能被B模板接受,但B模板的某些参数不能被A模板接受,那A就比B更特化。
咱们用这个逻辑套到你的两个模板上:
1. 用#1的参数匹配#2的模板
假设#1的模板参数是T,它的参数类型是T&(比如当T是X时,参数是X&)。把这个X&代入#2的参数模板U&&:
- 因为
X&是左值,转发引用的推导规则会把#2的U推导为X&,最终U&&折叠为X&,完全匹配。 - 也就是说,#1能处理的所有参数,#2都能处理。
2. 用#2的参数匹配#1的模板
假设#2的模板参数是U,它的参数类型是U&&(比如当U是int时,参数是int&&,也就是右值)。把这个int&&代入#1的参数模板T&:
int&&是右值引用,而T&是左值引用,左值引用无法绑定到右值引用类型的参数,推导失败。- 也就是说,#2能处理的参数(比如右值
int),#1处理不了。
结论:#1比#2更特化
因为#1能处理的参数是#2的子集,所以#1是更特化的模板。根据C++标准的重载决议规则,当特化签名相同时,优先选择更特化的模板,所以foo(i)最终会调用#1。
一句话总结
转发引用模板(#2)是一个"通用"模板,既能处理左值又能处理右值;而左值引用模板(#1)是一个"专用"模板,只能处理左值。当两者都能匹配同一个调用时,专用模板会胜出。
内容的提问来源于stack exchange,提问作者Mikhail
相关产品推荐
相关产品推荐

