模板偏特化
如果你认真学习了我们上一节内容,本节应当是十分简单的。
模板偏特化这个语法让模板实参具有一些相同特征可以自定义,而不是像全特化那样,必须是具体的什么类型,什么值。
比如:指针类型,这是一类类型,有 int*、double*、char*,以及自定义类型的指针等等,它们都属于指针这一类类型;可以使用偏特化对指针这一类类型进行定制。
模板偏特化使我们可以对具有相同的一类特征的类模板、变量模板进行定制行为。
变量模板偏特化
template<typename T>
const char* s = "?"; // 主模板
template<typename T>
const char* s<T*> = "pointer"; // 偏特化,对指针这一类类型
template<typename T>
const char* s<T[]> = "array"; // 偏特化,但是只是对 T[] 这一类类型,而不是数组类型,因为 int[] 和 int[N] 不是一个类型
std::cout << s<int> << '\n'; // ?
std::cout << s<int*> << '\n'; // pointer
std::cout << s<std::string*> << '\n'; // pointer
std::cout << s<int[]> << '\n'; // array
std::cout << s<double[]> << '\n'; // array
std::cout << s<int[1]> << '\n'; // ?语法就是正常写主模板那样,然后再定义这个 s 的时候,指明模板实参。或者你也可以定义常量的模板形参的模板,偏特化,都是一样的写法。
不过与全特化不同,全特化不会写 template<typename T>,它是直接 template<>,然后指明具体的模板实参。
它与全特化最大的不同在于,全特化基本必写 template<>,而且定义的时候(如 s)是指明具体的类型,而不是一类类型(T*、T[])。
我们再举个例子:
这种偏特化也是可以的,多个模板实参的情况下,对第一个模板实参为 int 的情况进行偏特化。
其他的各种形式无非都是我们提到的这两个示例的变种,类模板也不例外。
类模板偏特化
稍微提一下类外的写法,不过其实通常不推荐写到类外,目前还好;很多情况涉及大量模板的时候,类内声明写到类外非常的麻烦。
我们再举一个偏特化类模板中的类模板,全特化和偏特化一起使用的示例:
此示例无法在 gcc 通过编译,这是编译器 BUG 需要注意。
语法形式是简单的,不做过多的介绍。
其实和全特化没啥区别。
实现 std::is_same_v
std::is_same_v我们再写一个小示例,实现这个简单的 C++ 标准库设施。
这是对变量模板的偏特化,逻辑也很简单,如果两个模板类型参数的类型是一样的,就匹配到下面的偏特化,那么初始化就是 true,不然就是 false。
因为没有用到模板类型形参,所以我们只是写了 class 进行占位;这就和你声明函数的时候,如果形参没有用到,那么就不声明名字一样合理,比如 void f(int)。
声明为
inline的是因为 内联变量 (C++17 起)可以在被多个源文件包含的头文件中定义。也就是允许多次定义。
总结
我们在一开始的模板全特化花了很多时间讲解各种情况和细节,偏特化除了那个语法上,其他的各种形式并无区别,就不再介绍了。
本节我们给出了三个示例,也是最常见最基础的情况。我们要懂得变通,可能还有以此为基础的各种形式。值得注意的是,模板偏特化还可以和 SFINAE 一起使用,这会在我们后续的章节进行讲解,不用着急。
如还有需求,查看 cppreference。
最后强调一句:函数模板没有偏特化,只有类模板和变量模板可以。
Last updated