模板:
1) 区分模板参数(template parameter)和模板实参(template argument),前者用于模板的声明,后者用于模板的特化中:
template<typename T> //T是一个模板参数
class Heap {.....};
//........
Heap<double> dHeap; //double是一个模板实参
2) 区分模板名字(template name)和模板id(template id),前者只是一个简单的标识符,后者则是指附带有模板实参列表的模板名称:
Heap //模板名字
Heap<double> //模板id
3) 区分实例化(instantiation)和特化(specialization):
对模板的特化是指以一套模板实参提供给一个模板时所得到的东西。特化可是显式进行,也可以隐式进行。
例如当我们写Heap<int>时,是在使用int实参显式特化Heap类模板;写print(13.4)时,是在使用一个double实参隐式特化print函数模板。
类模板显式特化:
1)主模板:template <typename T> class Heap;
主模板仅仅被声明为用于特化,但它通常也提供定义:
template <typename T>
class Heap //模板名字
{
public:
void push(const T &val);
T pop();
bool empty() const {return h_.empty();}
private:
std::vector<T> h_;
};
我们可以提供一个针对指向字符的指针的显式特化版本,如下:
template <>
class Heap<const char *> //模板id
{
public:
void push(const char *val);
const char* pop();
bool empty() const {return h_.empty();}
private:
std::vector<const char*> h_;
};
其中的模板参数列表是空的,但要特化的模板实参则附在模板名字后面。这个类模板的显式特化版本其实并不是一个模板,因为此时没有剩下任何未指定的模板参数了。出于这个原因,类模板显式特化通常被称为“完全特化”,以便于局部特化区分开,后者是一个模板。
模板特化是一个提供了模板实参的模板名字,Heap<const char*>语法是一个模板特化,Heap<int>也是。然而,第一个对Heap的特化将不会导致对Heap模板的实例化(因为将会使用专为const char*定义的显式特化),但第二个特化将会导致对Heap主模板的实例化。
有了这个完全特化版本,我们就可以将针对const char*的Heap和其他Heap区分开了:
Heap<int> h1; //使用主模板
Heap<const char *> h2; //使用显式特化
Heap<char *> h3; //使用主模板
编译器根据主模板的声明来检查类模板特化。如果模板实参和主模板匹配,编译器将会查找一个可以精确匹配模板实参的显式特化。如果希望除了提供一个针对const char*的Heap外,还希望提供一个针对char*的Heap,就必须提供另一个显式特化了:
template <>
class Heap<char *> //模板id
{
public:
void push(char *val);
char *pop();
size_t size() const;
void capitalize();
//未提供empty函数
private:
std::vector<char *> h_;
};
C++并没有要求显式特化的接口必须和主模板的接口完全匹配。例如,第一个针对const char*的Heap显式特化中,函数push的形参类型被声明为const char*而不是const char *&;在针对char*的Heap特化中,我们添加了两个新函数size和capitalize,这是合法的,在某些情况下是非常有用的;但我们没有提供empty函数,这也是合法的,但通常是不可取的。
局部模板特化:
1)不能对函数模板进行局部特化,所能做的就是重载它们。
2)类模板局部特化跟完全特化一样,首先需要一个通用的主模板:
template <typename T> class Heap;
局部特化的代码如下:
template <typename T>
class Heap<T *> //模板id
{
public:
void push(const T *val);
T *pop();
bool empty() const {return h_.empty();}
private:
std::vector<T *> h_;
};
局部特化的语法类似于完全特化,但它的模板参数列表是非空的。就像完全特化一样,类模板名字是一个模板id而不是一个简单的模板名字。
和类模板的完全特化不同,局部特化是一个模板,在其成员的定义中,template关键字和参数列表时不可省略的。
和完全特化不同,这个版本的Heap的参数类型并没有被完全确定,它只是被部分地确定为T*,而T是一个未指定的类型。当使用任何指针类型来实例化一个Heap时,这个局部特化版本将优于主模板而被采用。也就是说,当模板实参类型是const char*或char*时,针对const char*和char*的完全特化版本的Heap又将优于局部特化而被采用。
Heap<std::string> h1; //使用主模板,T是std::string
Heap<std::string *> h2; //使用局部特化,T是std::string
Heap<int **> h3; //使用局部特化,T是int*
Heap<char *> h4; //使用针对char*的完全特化
Heap<char **> h5; //使用局部特化,T是char*
Heap<const int *> h6; //使用局部特化,T是const int
Heap<int (*)()> h7; //使用局部特化,T是int ()
主模板的完全特化或局部特化必须采用与主模板相同数量和类型的实参进行实例化,但它的模板参数列表并不需要具有和主模板相同的形式。对于Heap来说,主模板带有单个类型名字参数,因此Heap的任何完全特化或局部特化都必须采用单个类型名字实参来实例化:
template <typename T> class Heap;
所以,Heap的完全特化仍然带有单个类型名字模板实参,但模板参数列表不同于主模板的模板参数列表,因为完全特化的模板参数列表是空的:
template <> class Heap<char *>;
Heap的局部特化也必须带有单个类型名字模板实参,并且在其模板头部,模板参数列表可以带有单个类型名字实参:
template <typename T> class Heap<T *>;
但它未必非得如此:
template <typename T, int n> class Heap<T[n]>;
当使用一个数组类型来特化Heap时,将会选用这个局部特化,例如:
Heap<float *[6]> h8; //使用局部特化,T是float*且n为6
更有甚者:
template <typename R, typename A1, typename A2>
class Heap<R(*)(A1, A2)>;
template<class C, typename T>
class Heap<T C::*>;
有了这些额外的局部特化,就可以采用“指向带有两个参数的非成员函数”的指针对Heap进行特化;采用指向数据成员的指针进行特化:
Heap<char *(*)(int, int)> h9;
//使用局部特化,R是char*,A1和A2是int
Heap<std::string Name::*> h10;
//使用局部特化,T是string,C是name