如果我们在代码中使用万能引用型别作为函数重载的一个特征标,这样在我们使用该重载函数时,实际调用的函数可能与我们期望不符:
示例代码如下:
#include <memory>
#include <iostream>
#include <set>
using namespace std;
//万能引用型别重载
template <class T>
void MaKeZiYuan(T&& t) {
cout << "MaKeZiYuan(T&& t)" << endl;
};
//参数为int
void MaKeZiYuan(int t) {
cout << "MaKeZiYuan(int t)" << endl;;
};
int main()
{
MaKeZiYuan("123"); //参数为char*,匹配万能引用型别
MaKeZiYuan(456); //参数为整型常量(int),匹配int型别
int j = 456;
MaKeZiYuan(j); //参数为int,匹配int型别
short i = 1;
MaKeZiYuan(i); //参数为short,匹配万能引用型别
long k = 1;
MaKeZiYuan(k); //参数为long,匹配万能引用型别
system("pause");
}
执行结果如下:
前三个调用我们都能理解,但是最后两个为什么匹配到了万能引用呢?因为当实参参数为long或者short时,从实参到形参,需要进行提升或窄化后才能匹配到int形参,而对于形参类别为T&&的万能引用,则直接将T推导为long或者short后,精确匹配。所以按照普适的重载决议规则,精确匹配优先于提升或窄化后匹配,所以才会调用到万能引用型别的重载。
这样的问题同样存在于我们类的构造函数中,如果使用万能引用作为构造函数的模板,那么你将很难真正调用到其他构造函数。例如:
class Person{
template<typename T>
explicit Person(T&& n):name(std::forward<T>(n)){} // 完美转发的构造函数
Person(const Person& rhs); // 复制构造函数,编译器生成
Person(Person&& rhs); // 移动构造函数,编译器生成
};
当我们执行如下代码,理论上在cloneOfP中应该调用的是复制构造函数,但是实际上调用的却是完美转发构造函数,编译期的推理过程如下:p是一个非常量左值,它既可以传递给完美转发构造函数,也可以转发给复制构造函数,但是如果调用的是复制构造函数,则需要先对p加上const修饰词,而实例化后的模板却不需要添加任何修饰词,所以模板生成的重载版本才是最佳选择。
Person p("Wxx");
auto cloneOfP(p);
当然,问题也很好解决,只需要将实参与欲调用的函数的形参完全一致,这样就可以调用到你期望的函数了,因为即使万能引用型别函数也可以完美匹配你的实参,但是按照普适的重载决议另一条规则,当一个模板实例化函数与一个非模板实例化函数具有相等的匹配程度时,优先使用非模板函数作为重载函数。
至此,我们应该形成一个潜意思:形参时万能引用的函数时最贪婪的,它会在具现的过程中,和几乎任何实参型别都会产生精确匹配,不到万不得已,不要使用。