模板偏特化

如果你认真学习了我们上一节内容,本节应当是十分简单的。

模板偏特化这个语法让模板实参具有一些相同特征可以自定义,而不是像全特化那样,必须是具体的什么类型,什么值。

比如:指针类型,这是一类类型,有 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

我们再写一个小示例,实现这个简单的 C++ 标准库设施。

这是对变量模板的偏特化,逻辑也很简单,如果两个模板类型参数的类型是一样的,就匹配到下面的偏特化,那么初始化就是 true,不然就是 false

因为没有用到模板类型形参,所以我们只是写了 class 进行占位;这就和你声明函数的时候,如果形参没有用到,那么就不声明名字一样合理,比如 void f(int)

声明为 inline 的是因为 内联变量 (C++17 起)可以在被多个源文件包含的头文件中定义。也就是允许多次定义。

总结

我们在一开始的模板全特化花了很多时间讲解各种情况和细节,偏特化除了那个语法上,其他的各种形式并无区别,就不再介绍了。

本节我们给出了三个示例,也是最常见最基础的情况。我们要懂得变通,可能还有以此为基础的各种形式。值得注意的是,模板偏特化还可以和 SFINAE 一起使用,这会在我们后续的章节进行讲解,不用着急。

如还有需求,查看 cppreference

最后强调一句:函数模板没有偏特化,只有类模板和变量模板可以

Last updated