1、
所谓模板特例,是针对符合某种条件的模板参数值集合另外声明的模板实现变体。
template<typename T> class my_vector;
template<> class my_vector<bool>;
2、特例的多种写法
template<typename T, int i> struct S1; template<typename T, int i, template<typename, int> class SP> struct S;//这里先定义一个模板通例 template<int i, template<typename, int> class SP> struct S<char, i, SP>;//省略了typename T template<typename T, int i, template<typename, int> class SP> struct S<const T, i, SP>;//约束第一个模板参数的类型必须用const修饰 template<> struct S<char, 10, S1>;//完全特例,其匹配式中没有用到任何模板参数,所有项目都是确定的类型或值或模板。故其模板参数列表为空。相应的,我们将模板参数列表不为空的特例成为部分特例 template<typename T> struct S<S1<T, 10>, 10, S1>;//难点理解:同特例2一样,这里也是对T的一种约束,它要求S这个模板的第一个参数必须是S1<T, 10>模板的实例,而第三个参数要是S1型的某个模板,这里不要搞混(搞混的话就回头去看模板型模板参数)
3、特例的匹配规则
template<typename T0, typename T1, typename T2> struct S { std::string id() { return "General"; } }; template<typename T0, typename T1> struct S<T0, T1, char> { std::string id() { return "Specialization #1"; } }; template<typename T0> struct S<T0, char, char> { std::string id() { return "Specialization #2"; } }; template<typename T> struct S<int, T, T> { std::string id() { return "Specialization #3"; } }; void main() { using namespace std; printf("%s ", S<float, float, float>().id().c_str());//匹配通例 printf("%s ", S<int, int, int>().id().c_str());//只与特例3匹配 printf("%s ", S<int, int, char>().id().c_str());//与特例1匹配,因为特例3要求后两个类型是相同的 printf("%s ", S<char, char, char>().id().c_str());//与特例1和特例2均匹配,但特例2比特例1更加特殊 printf("%s ", S<int, char, char>().id().c_str());//在特例2和特例3之间无法判断谁更特殊 getchar(); }
4、函数模板的特例与重载
函数模板特例的写法与类模板特例的写法十分相似。
函数重载是要匹配函数的参数,而函数模板特例则是要匹配模板参数。粗看二者似乎并不想干,但C++中海油一个函数模板参数推导机制。当没有显式给定模板实参值时,编译器会尝试由函数调用的实参类型推导出模板参数值。有此推导机制,函数模板参数值在某些情况下也可与函数调用的实参相关。
特例与重载这两种既想干又不完全等价的机制并存,使得处理函数模板变得异常复杂且潜藏众多冲突。为化简问题,C++标准中只允许为函数模板声明完全特例,而禁止为其声明部分特例。大多数需要用到部分 特例的情况都可以利用函数模板重载来实现。
区分模板特例与重载:
准则一:两候选函数中如果有一方其形参列表个类型与调用实参列表各类型更匹配,则淘汰另一方。
准则二:两函数如果其形参列表类型同等匹配实参列表类型时,若一方为函数模板实例而另一方为非模板函数,则去非模板函数而淘汰函数模板实例。
准则三:两函数如果其形参列表类型同等匹配实参列表类型时,若两者均为函数模板实例,则去更为特殊的一方淘汰另一方。
template<typename T> void func(T v) { printf("#1 "); } template<> void func(float v) { printf("#2 "); } void func(float v) { printf("#3 "); } void main() { func(1);//只有#1才有可能是int,其它全是float func(1.);//1.是double类型,只有#1才有可能是double,其它全是float func(1.f);//#1和#2和#3都与实参类型完全匹配。#2比#1更加特殊,淘汰掉#1。#3与#2之间比较选择非模板函数#3。 func<>(1.f);//这里显式指定要使用的是一个模板实例 getchar(); }
编译期递归逻辑:
#2与#3接口完全一致。也就是说,你可以完全不用#3这个完全模板特例,而只使用#2这样的重载替代模板完全特例就好了。因为,前面的#2中,func(float v)可以通过实参的float推导出模板中的T是float,所以使用#3func重载的效果和使用完全模板特例是一样的,没必要再设完全模板特例了,(也就是说通过实际使用参数float v已经推断出了T,但是如果实参和模板参数没有联系呢?)那么模板完全特例存在的意义为何?
比如说,有模板参数不与函数参数类型相关,此时的模板特例不能用函数重载替代。例如,模板有个非模板型模板参数,则显然不能通过调用实参类型推导,而需要在模板实参列表中给定明确的值。
举例,
template<int i>
void print(float f)
{
//这里边使用i
}
这个模板的模板参数与函数实参没有任何关系,且唯一的模板参数是非模板型模板参数。那么根据 i 的取值不同,其模板实例的意义不同。你给print<1>~print(100)都设为特例则有可能有100个不同的意义,所以如果有一个重载函数void print(float f),你知道这个print()要在何时执行吗哪一个吗?
print<1>(float);//完全特例
print<100>(float);//完全特例
假如有一个,print(float)你不知道这里的值到底是1-100实例中的哪一个,这里就不能用函数重载替代完全模板特例。
现在,print(1.)就完全不能代替模板了。
实例如下:
template<int i> void print() { print<i - 1>(); printf("%d ", i); } template<> void print<1>() { printf("1! "); } void main() { print<10>(); getchar(); }