编写单一模板,使之对任何可能的模板实参都是最合适的,都能实例化,这并不总是能办到的。当我们不能(或不希望)使用模板版本时,可以定义类或函数模板的一个特例化版本。
定义函数模板特例化
当我们特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参。为了指出我们正在实例化一个模板,应使用关键字template后跟一个空尖括号对(<>)
template<typename T> int compare(const T&, const T&);
template <>
int compare(const char* const &p1, const char* const &p2)
{
return strcmp(p1, p2);
}
当我们定义一个特例化版本时,函数参数类型必须与一个先前声明的模板中对应的类型匹配(类型别名、模板参数类型、指针及const之间的相互作用会让人惊讶)。上例中T为char* const。函数要求一个指向char* const类型的const版本的引用,我们在特例化版本中使用的类型是一个指向const char的const指针的引用。
函数重载和模板特例化
当定义函数模板的特例化版本时,我们本质上接管了编译器的工作。即,我们为原模板的一个特殊实例提供了定义。特例化的本质是实例化一个模板,而非重载它。因此,特例化不影响函数匹配。我们将一个特殊的函数定义为一个特例化版本还是一个独立的非模板函数,会影响到函数匹配。
为了特例化一个模板,原模板的声明必须在作用域中。而且,在任何使用模板实例的代码之前,特例化版本的什么也必须在作用域中。(模板和其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是这些模板的特例化版本)
类模板特例化
为标准库hash模板定义一个特例化版本,可以用它来将Sales_data对象保存到无序容器中。一个特例化hash类必须定义:
- 一个重载的调用运算符,接受一个容器关键字类型的对象,返回一个size_t
- 两个类型成员,result_type和argument_type,分别调用运算符的返回类型和参数类型
- 默认构造函数和拷贝赋值运算符
在定义此特例化版本hash时,唯一复杂的地方是:必须在原模板定义所在的命名空间中特例化它。
namespace std {
template<>
struct hash<Sales_data>
{
typedef size_t result_type;
typedef Sales_data argument_type;
size_t operator()(const Sales_data &s) const;
};
size_t
hash<Sales_data>::operator()(const Sales_data &s) const
{
return hash<string>()(s.bookNo) ^
hash<unsigned>()(s.units_sold) ^
hash<double>()(s.revenue);
}
}
由于hash使用了Sales_data的私有成员,所以必须声明为Sales_data的友元:
template <class T> class std::hash;
class Sales_data{
friend class std::hash<Sales_data>;
};
类模板部分特例化
与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以只指定一部分而非所有模板参数,或是参数的一部分而非全部特性。一个类模板的部分特例化(partial specialization)本身是一个模板,使用它时用户还必须为那些在特例化版本中未指定的模板参数提供实参。
template <class T> struct remove_reference {
typedef T type;
};
//部分特例化版本,将用于左值引用和右值引用
template <class T> struct remove_reference<T&>
{
typedef T type;
};
template <class T> struct remove_reference<T&&>
{
typedef T type;
}
int i;
//decltype(42)为int,使用原始模板
remove_reference<decltype(42)>::type a;
//decltype(i)为int&,使用T&部分特例化版本
remove_reference<decltype(i)>::type b;
//decltype(std::move(i))为int&&,使用T&&部分特例化版本
remove_reference<decltype(std::move(i))>::type c;
特例化成员而不是类
我们可以只特例化特定成员函数而不是特例化整个模板。
template <typename T> struct Foo {
Foo(const T &t = T()) : mem(t) {}
void Bar() { /* */}
T mem;
};
template<>
void Foo<int>::Bar()
{
}
当我们使用int之外的任何类型使用Foo时,其他成员像往常一样进行实例化。当我们用int使用Foo时,Bar之外的成员像往常一样进行实例化;如果我们使用Foo<int>的成员Bar,则会使用我们定义的特例化版本。