模板全特化
本节将介绍模板全特化。
其实很多东西都能进行全特化,不过我们围绕着之前的内容:函数模板、类模板、变量模板来展开。
函数模板全特化
给出这样一个函数模板 f,你可以看到,它的逻辑是返回两个对象相加的结果,那么如果我有一个需求:“如果我传的是一个 double 一个 int 类型,那么就让它们返回相减的结果”。
template<typename T,typename T2>
auto f(const T& a, const T2& b) {
return a + b;
}C++14 允许函数返回声明的 auto 占位符自行推导类型。
这种定制的需求很常见,此时我们就需要使用到模板全特化:
template<>
auto f(const double& a, const int& b){
return a - b;
}当特化函数模板时,如果模板实参推导能通过函数实参提供,那么就可以忽略它的模板实参。
语法很简单,只需要先写一个 template<> 后面再实现这个函数即可。
不过我们其实有两种写法的,比如上面那个示例,我们还可以写明模板实参。
个人建议写明更加明确,因为很多时候模板实参只是函数形参类型的一部分而已,比如上面的 const double&、const int& 只有 double 、int 是模板实参。
使用:
类模板全特化
和函数模板一样,类模板一样可以进行全特化。
我们使用全特化,实现了一个 is_void 判断模板类型实参是不是 void 类型。
虽然很简单,但我们还是稍微强调一下:同一个类模板实例化的不同的类,彼此之间毫无关系,而静态数据成员是属于类的,而不是模板类;模板类实例化的不同的类,它们的静态数据成员不是同一个,请注意。
我们知道标准库在 C++17 引入了 is_xxx 的 _v 的版本,就不需要再写 ::value 了。所以我们也可以这么做,这会使用到变量模板。
我们再给出一个简单的示例:
我们要明白,写一个类的全特化,就相当于写一个新的类一样,你可以自己定义任何东西,不管是函数、数据成员、静态数据成员,等等;根据自己的需求。
变量模板全特化
语法形式和前面函数模板、类模板都类似,很简单,这个变量模板是类型形参。我们特化了变量模板 s 的模板实参为 void 与 int 的情况,修改 s 的初始化器,让它的值不同。
上面的变量模板,模板是类型形参,我们根据类型进行全特化。我们特化了 is_void_v 的模板实参为 void 的情况,让 is_void_v 值 为 true。
细节
前面函数、类、变量模板的全特化都讲的很简单,示例也很简单,或者说语法本身大多数时候就是简单的。我们在这里进行一些更多的补充一些细节。
特化必须在导致隐式实例化的首次使用之前,在每个发生这种使用的翻译单元中声明:
如果 f2 中的调用换成 f(1.) 就没问题,它隐式实例化的就是 f<double> 了。
只有声明没有定义的模板特化可以像其他不完整类型一样使用(例如可以使用到它的指针和引用):
函数模板和变量模板的显式特化是否为 inline/constexpr/constinit/consteval 只与显式特化自身有关,主模板的声明是否带有对应说明符对它没有影响。模板声明中出现的属性在它的显式特化中也没有效果:
如果主模板有 constexpr 属性,那么模板实例化的,如 g<double> 自然也是附带了 constexpr,但是如果其特化没有,那么以特化为准(如 g<int>)。
特化的成员
特化成员的写法略显繁杂,但是只要明白其逻辑,一切就会很简单。
主模板
特化模板类。
A<void>。
特化成员类。设置
A<char>的情况下B类的定义。
特化成员类模板。设置
A<int>情况下模板类C的定义。
特化类的成员函数模板
其实语法和普通特化函数模板没什么区别,类外的话那就指明函数模板是在 X 类中。
特化类模板的成员函数模板
成员或成员模板可以在多个外围类模板内嵌套。在这种成员的显式特化中,对每个显式特化的外围类模板都有一个 template<>。
其实就是注意有几层那就多套几个 template<>,并且指明模板类的模板实参。下面这样:就是自定义了 X<void> 且 f<double> 的情况下的函数。
视频中的代码,模板类和成员函数模板都用的
T,只能在msvc下运行,gcc 与 clang 有歧义,需要注意。
类内对成员函数
f的特化,在gcc无法通过编译,根据考察,这是一个很多年前就有的BUG,使用gcc的开发者自行注意。
总结
我们省略了一些内容,但是以上在我看来也完全足够各位学习使用了。如有需求,查看 cppreference。
模板全特化的语法主要核心在于 template<>,以及你需要注意,你到底要写几个 template<>。其他的都很简单。
Last updated