1 第十二章 特化与重载
1.1 重载函数模板
和一般的函数重载类似,函数模板也可以进行重载,比如下面的两个f,这是两个同名函数,1和2并没有关系,2不是1的局部特化。2是1的一个重载。
//1
template<typename T1, typename T2>
void g(T1 a, T2 b){
std::cout<<"void g(T1 a, T2 b)"<<std::endl;
}
//2
template<typename T>
void g(T a, int b){
std::cout<<"void g(T a, int b)"<<std::endl;
};
1.2 显式特化
1.2.1 全局的函数模板特化
其实以前我没有听说过函数模板也能特化,我一直以为函数模板只能重载。
对于函数模板来说,下面的几个定义可以位于同一个翻译单元:
template<typename T1, typename T2>
void g(T1 a, T2 b){
std::cout<<"void g(T1 a, T2 b)"<<std::endl;
}
template<typename T>
void g(T a, int b){
std::cout<<"void g(T a, int b)"<<std::endl;
};
template<>
void g(int a, int b){
std::cout<<"template<> void g(int a, int b)"<<std::endl;
};
void g(int a, int b){
std::cout<<"void g(int a, int b)"<<std::endl;
}
对于编译器来说,排序规则是这样的:局部特化比一般模板特殊;全特化比局部特化特殊;非模板函数比全特化特殊。
全局函数模板特化与类模板特化的区别在于:函数模板引入了重载和实参演绎两个概念。
有了实参演绎,我们就可以用它来确定模板的特殊化版本的实参。这样在函数的全局特化中,我们就可以不用显式声明函数的模板参数。
比如上面的:
template<>
void g(int a, int b){ //1
std::cout<<"template<> void g(int a, int b)"<<std::endl;
};
他其实等同于下面的代码:
template<>
void g<int,int>(int a, int b){ //2
std::cout<<"template<> void g(int a, int b)"<<std::endl;
};
这点可以参考VS2005的MSDN:ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_vclang/html/eb0fcb73-eaed-42a1-9b83-14b055a34bf8.htm
但是有一点很奇怪,既然上面的两个是等同的,如果我把他们放在同一个头文件中来定义并不会报错。
还有,在以上两个定义都存在的情况下,如果我这样调用:
g<int,int>(1,1);
那么调用的实际上是2,而如果我使用g(1,1)则调用的实际上是2。
更进一步:
如果将2删除,然后再次调用g<int,int>(1,1),那么调用的实际上是:void g(T1 a, T2 b) ,从上面的这些分析我们可以看出:
template<>
void g(int a, int b)
实际上没有被看作是模板。他的优先级介于模板特化和重载的一般函数之间。
上面这个例子的原因是这样的,让我们来整理一下思路,整理之后的代码如下,我们定义了下面4个模板,将那个普通函数的重载删除:
//1
template<typename T1, typename T2>
void g(T1 a, T2 b){
std::cout<<"void g(T1 a, T2 b)"<<std::endl;
}
//这是另外一个模板,他并不是1的局部特化
//因为特化要求模板的参数个数相同
//1有两个模板参数T1, T2
//这里只有一个模板参数
//这属于1的一个函数模板重载
//2
template<typename T>
void g(T a, int b){
std::cout<<"void g(T a, int b)"<<std::endl;
};
//3, 这个其实是的特化
template<>
void g(int a, int b){
std::cout<<"template<> void g(int a, int b)"<<std::endl;
};
//4,这个其实是的特化
template<>
void g<int,int>(int a, int b){
std::cout<<"template<> void g(int a, int b)"<<std::endl;
};
所以:
int _tmain(int argc, _TCHAR* argv[])
{
double d = 1;
g('a', d); //等同于g<char,double>('a',d);
g<char>('a',1); //等同于g<char,int>('a',1);
g<int,int>(1,1);
g(1,1); //等同于g<int>(1,1);
return 0;
}
1.2.2 全局的类模板特化
来看一个特化的例子:
template<typename T>
class Types{
public:
typedef int I;
};
template<typename T ,typename U = typename Types<T>::I>
class S; //(1)
template<>
class S<void>{ //(2)
};
template<>
class S<char, char>;//(3)
int _tmain(int argc, _TCHAR* argv[])
{
S<int>* pi;
S<void>* pv;
S<void, int> sv; //使用2
S<void, char> sc;//
return 0;
}
其中语句S<void, char> sc;会出错: error C2079: 'sc' uses undefined class 'S<T,U>'。
我原来以为该语句会使用S<void>特化,但是看来并不是。所以,怀疑上文的S<void>定义就相当于下面的定义:
template<>
class S<void, int>{};
于是在正文中加上上面这段代码,重新编译,果然报错:error C2766: explicit specialization; 'S<void>' has already been defined 。
对于特化的声明而言,因为它并不是模板声明,所以应该使用普通成员定义语法来定义全局类模板特化的成员。
template<>
class S<void>{ //(2)
public:
void f();
};
//此处没有template<>
void S<void>::f()
{}
注意到其中的f函数的定义。
可以用全局模板特化来代替对应泛型模板的某个实例化体。然而,全局模板特化和由模板特化生成的实例化版本是不能够共同存在于一个程序中的。例如:
template<typename T>
class Invalid{};
Invalid<int> x1;
template<>
class Invalid<int>;
那么编译器会报如下错误: error C2908: explicit specialization; 'Invalid<T>' has already been instantiated。
遗憾的是,如果他们在不同的翻译单元出现,则编译器就发现不了这种错误。我们来看一个例子:
//Chapter12.h
template<typename T>
class Danger{
public:
enum { max = 10};;
};
char buffer[Danger<void>::max];
void clear(char* buf);
//chapter12.cpp
template<typename T>
class Danger;
template<>
class Danger<void>{
public:
enum {max = 100};
};
void clear(char* buf)
{
for(int k = 0; k < Danger<void>::max; ++k)
buf[k] = '"0';
}
//main函数
#include “chapter12.h”
int _tmain(int argc, _TCHAR* argv[])
{
clear(buffer);
return 0;
}
那么,在clear执行的时候,max的值其实是100。这就可能会导致内存访问错误。
在使用特化的时候,我们需要特别小心,并且确认特化的声明对于泛型模板的所有用户都是可见的。这就意味着:在模板声明所在头文件中,特化的声明通常应该位于模板声明的后面。