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