特化和部分特化(部分特化也叫偏特化)
比如下面这个模板比较函数
template<typename T> bool compare(T a, T b) { return a > b; } int main() { compare(10, 20); //实例化出const char*的比较函数,比较的是地址,而不是字典序 compare("aaa", "bbb"); }
compare("aaa", "bbb");
比较的是地址,而不是字典序,所以需要实现对字符串比较的特化版本:
template<typename T> bool compare(T a, T b) { cout << "template compare" << endl; return a > b; } template<> bool compare<const char*>(const char*a, const char*b) { cout << "compare<const char*>" << endl; return strcmp(a, b) > 0; } int main() { compare(10, 20); compare("aaa", "bbb"); }
类模板特化:
template<typename T> class Vector { public: Vector() { cout << "call Vector template init" << endl; } }; //下面这个是对char*类型提供的完全特化版本 template<> class Vector<char*> { public: Vector() { cout << "call Vector<char*> init" << endl; } }; //下面这个是对指针类型提供的部分特化版本 //只知道是指针类型,不知道具体是什么类型 template<typename Ty> class Vector<Ty*> { public: Vector() { cout << "call Vector<Ty*> init" << endl; } }; //部分特化,针对有返回值,有两个形参变量的函数提供的函数指针 template<typename R, typename A1, typename A2> class Vector<R(*)(A1, A2)> { public: Vector() { cout << "call Vector<R(*)(A1, A2)> init" << endl; } }; //完全特化 //template<> //class Vector<int(*)(int, int)> //针对函数(有一个返回值,有两个形参变量)类型提供的部分特化 template<typename R, typename A1, typename A2> class Vector<R(A1, A2)> { public: Vector() { cout << "call Vector<R(A1, A2)> init" << endl; } }; int sum(int a, int b) { return a + b; } int main() { Vector<int> vec1; //优先使用完全特例化版本 Vector<char*> vec2; //有部分特化就使用部分特化,没有的话就是用原模版 Vector<int*> vec3; Vector<int(*)(int, int)> vec4; Vector<int(int, int)> vec5; //区分函数类型和函数指针类型 typedef int(*PFUNC1)(int, int); PFUNC1 pfunc = sum; cout << pfunc(10, 20) << endl; typedef int PFUNC2(int, int); PFUNC2 *pfunc2 = sum; cout << (*pfunc2)(10, 20) << endl; return 0; }
关于特化和部分特化(偏特化),其实就是看是否还有可推导的内容,特化实际上是为了应对特殊的场景。
模板的实参推演
int sum(int a, int b) { return a + b; } template<typename T> void func(T a) { cout << typeid(T).name() << endl; } template<typename R, typename A1, typename A2> void func2(R(*a)(A1, A2)) { cout << typeid(R).name() << endl; cout << typeid(A1).name() << endl; cout << typeid(A2).name() << endl; } class Test { public: int sum(int a, int b) { return a + b; }; }; template<typename R, typename T, typename A1, typename A2> void func3(R(T::*a)(A1, A2)) { cout << typeid(R).name() << endl; cout << typeid(T).name() << endl; cout << typeid(A1).name() << endl; cout << typeid(A2).name() << endl; } int main() { func(10); func("aaa"); func(sum); //int (__cdecl*)(int,int) func2(sum); func(&Test::sum); func3(&Test::sum); return 0; }
引用折叠
首先,&&
在模板中并不是右值引用的意思,只是说明要传一个引用。
比如,现在有这样一个例子:
namespace gao { template<typename T> void func(T &&a) { } } int main() { int n = 123; gao::func(n); gao::func(123); return 0; }
我们分别注释掉两个调用,并用nm
命令查看实例化出来的函数发现:
gao::func(n); -> 0000000000000000 W void gao::func<int&>(int&) gao::func(123); -> 0000000000000000 W void gao::func<int>(int&&)
对于func(n)
,T类型被推导为int&
。
对于func(123)
,T类型被推导为int
。
按照我们开始说的"&&在模板中并不是右值引用的意思,只是说明要传一个引用"
n是左值,所以要对应左值引用,当T被推导为int&后,形参就变成了func(int& &&a)
同时,模板中奇数个&被标识为左值引用,偶数个&被标识为右值引用,所以三个&,引用折叠,最后函数形参就变为了左值引用。
123是右值,T被推导为int后,形参就变成了func(int &&)
右值引用。
这时,如果想使用T类型定义变量时,也会报错:
namespace gao { template<typename T> void func(T &&a) { T b; } }
报错信息:
error: ‘b’ declared as reference but not initialized
因为,T被func(n)
推导为int&类型,而int& b必须得有初始化值。
其实模板中提供了一种方法可以移除引用的类型:
namespace gao { template<typename T> void func(T &&a) { typename remove_reference<T>::type b; } }
这样,就不会报错了。