模板的全特化与偏特化 | C & C++
模板机制为 C++ 提供了泛型编程的方式,在减少代码冗余的同时仍然可以提供类型安全。特化必须在同一命名空间下进行,可以特化类模板也可以特化函数模板,但类模板可以偏特化和全特化,而函数模板只能全特化。C++ 的模板机制被证明是图灵完备的,即可以通过模板元编程(template meta programming)的方式在编译期做任何计算。
模板的声明
类模板和函数模板的声明方式是一样的,在类定义/模板定义之前声明模板参数列表。例如:
1 | // 类模板 |
全特化
通过全特化一个模板,可以对一个特定参数集合自定义当前模板,类模板和函数模板都可以全特化。全特化的模板参数列表应当是空的,并且应当给出“模板实参”列表:
1 | // 全特化模板类 |
类模板的全特化时在类名后给出了“模板实参”,但函数模板的函数名后没有给出“模板实参”。这是因为编译器根据 int max(const int, const int)
的函数签名可以推导出来它是 T max(const T, const T)
的特化。
注意:使用时需要将全特化模板函数的实现放在使用该函数之前,否则会报 specialization xxx after instantiation
的错误。或者在使用之前进行全特化声明也可以。
特化的歧义
上述函数模板不需指定”模板实参”是因为编译器可以通过函数签名来推导,但有时这一过程是有歧义的:
1 | template <typename T> |
此时编译器不知道 f()
是从 f<T>()
特化来的,编译时会提示错误 error: no function template matches function template specialization 'f'
。这时我们便需要显式指定“模板实参”:
1 | template <typename T> |
偏特化(部分特化)
偏特化是全特化的一种特殊情况,通用性要低于全特化,可以理解为部分特化。类似于全特化,偏特化也是为了给自定义一个参数集合的模板,但偏特化后的模板需要进一步的实例化才能形成确定的签名。函数模板不允许偏特化。偏特化也是以 template
来声明的,需要给出剩余的“模板形参”和必要的“模板实参”:
1 | template <class T2> |
函数模板是不允许偏特化的,下面的声明会编译错:
1 | // 全特化模板函数 |
但函数允许重载,声明另一个函数模板即可替代偏特化的需要:
1 | template <class T2> |
多数情况下函数模板重载就可以完成函数偏特化的需要,一个例外便是 std
命名空间。std
是一个特殊的命名空间,用户可以特化其中的模板,但不允许添加模板(其实任何内容都是禁止添加的)。
1 | template <class T, bool USESPE> |
至于为什么模板函数不能偏特化,这仅仅是 C++ 标准不允许。可能是因为使用重载也可以达到这个目的。
函数模板的特化
1 | template<class T> |
1 | template<class T> |
可以看到两个例子里面的特化函数是一模一样的,只是相应的基模板不同。出现这种现象是因为在编译器解析重载的时候只考虑基模板(当然如果有非模板函数更合适,则选择该非模板函数)。当基模板被选定之后,编译器才会去查看有没有合适的特化版本。
函数模板的特化函数不参与重载的原因很简单:如果你为函数模板写了特化函数,那么你就希望这个特化函数被调用,而如果其他人为另一个函数模板写了个特化函数,其他人也希望这个特化函数被调用,那么结果可能会不如你所愿,所以 C++ 标准禁止了特化函数参与重载。
参考
模板的全特化与偏特化 | C & C++