模板分为函数模板和类模板,特化分为全特化和偏特化(Partial Specialization)。其中,类模板有全特化和偏特化,函数模板只能全特化。使用模板的时候,我们的目的就是希望可以不用每个类型实现一遍,而是用一个模板代替这个类型。如果所有类型的实现都是一个统一的一样的实现,就不需要模板特化或者偏特化了,但是大多数情况是肯定有特殊版本需要特殊处理的,有两个典型例子:
1、hash<_Kty>
:模板全特化
2、vector<bool>
:模板偏特化
文章分为 3 部分:
1、模板全特化(函数模板只能全特化)
1.1 类模板全特化
1.2 函数模板全特化
2、模板偏特化
2.1 个数上偏特化
2.2 范围上偏特化
3、总结
一、模板全特化
1.1 类模板全特化
C++11中新加入的容器 unordered_set
、unordered_map
以及他们的 multi 版本,底层都是哈希表,模板参数如下:
template<class _Kty,
class _Hasher = hash<_Kty>,
class _Keyeq = equal_to<_Kty>,
class _Alloc = allocator<_Kty>>
可以看到第一个参数就是我们要填入的 unordered_set
要存放的元素的类型,第二个参数就是 hash 函数,默认使用 hash<_Kty>
,我们跳到 hash<_Kty>
实现部分可以看到:
除了模板形式的 hash<_Kty>
,还有很多特化版本,比如 hash<float>
。模板版本就是 template<class _Kty>
:
template<class _Kty>
struct hash : __blalbla // 一大堆,省略掉先
{
// hash functor primary template (handles enums, integrals, and pointers)
static size_t _Do_hash(const _Kty& _Keyval) noexcept {
// hash _Keyval to size_t value by pseudorandomizing transform
return (_Hash_representation(_Keyval));
}
};
而特化版本,则是在类前边加上 template<>
,告诉编译器,下边这个是特化版本,而且后边写的时候要明确写出 struct hash<float>
,告诉编译器特化的版本的模板类型:
template<>
struct hash<float>
{
// hash functor for float
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef float argument_type;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef size_t result_type;
_NODISCARD size_t operator()(const float _Keyval) const noexcept {
// hash _Keyval to size_t value by pseudorandomizing transform
return (_Hash_representation(_Keyval == 0.0F ? 0.0F : _Keyval)); // map -0 to 0
}
};
1.2 函数模板全特化
C++不允许函数模板偏特化,可能是因为函数可以直接重载?。
//模板函数
template<typename T1, typename T2>
void fun(T1 a, T2 b) {
cout << "模板函数" << endl;
}
//全特化
template<>
void fun<int, char >(int a, char b) {
cout << "全特化" << endl;
}
二、模板偏特化 Partial Specialization
模板偏特化是指模板列表中一部分特化,一部分不特化,比如 vector<bool>
。我们知道 vector<bool>
和其他的 vector 不一样,他使用一个 bit 来存储一个元素,因为只有 true
和 false
(C++ STL 里边所有容器,只有 vector 对 bool 进行了这样的特化)。
模板偏特化分为:范围上偏特化,个数上偏特化。
2.1 个数上偏特化
正常的 vector 如下(比如一个 vector<int> vi;
,我们跳过去):
// CLASS TEMPLATE vector
template<class _Ty, class _Alloc = allocator<_Ty>>
class vector // : blabla // 继承这里不关心,省略掉
{
// varying size array of values
// ...
}
正常的如 vector<int> vi;
是有两个模板参数的,第一个是元素类型,第二个是存储方式,默认是 allocator<_Ty>
,我们在声明一个 vector<bool> vb;
,跳过去看:
// CLASS vector<bool>
template<class _Alloc>
class vector<bool, _Alloc> : public _Vb_val<_Alloc>
{
// varying size array of bits
// ...
}
类前边只写了一个模板 template<class _Alloc>
,而在下边写明了 class vector<bool, _Alloc>
,这样编译器就知道是偏特化了。这种模板参数个数变化的,就是个数上的偏特化。
2.1 范围上偏特化
下边这样,模板参数个数没有变化,而是从值变成指针,就是范围上偏特化。
template<typename T>
class C
{
};
template<typename T>
class C<T*>
{
};
int main() {
C<int> c;
C<int*> cp;
}
三、总结
- 为什么要特化:
如果对于一个模板功能,或者模板类,如果对于特定类型有更好的、更合适的或者有特殊需求的实现,就应该提供特化版本,让编译器知道; - 模板优先级
没有特化的版本,就叫 模板泛化(优先级:全特化 > 偏特化 > 泛化),所以才能针对特定类型进行特定实现; - 想到新的再写。。。