> For the complete documentation index, see [llms.txt](https://mq-bai.gitbook.io/modern-cpp-templates-cookbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mq-bai.gitbook.io/modern-cpp-templates-cookbook/05-mu-ban-pian-te-hua.md).

# 模板偏特化

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

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

比如：指针类型，这是一类类型，有 `int*`、`double*`、`char*`，以及自定义类型的指针等等，它们都属于指针这一类类型；可以使用偏特化对指针这一类类型进行定制。

* ***模板偏特化使我们可以对具有相同的一类特征的类模板、变量模板进行定制行为。***

## 变量模板偏特化

```cpp
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\[]）。

***

我们再举个例子：

```cpp
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` 的情况进行偏特化。

其他的各种形式无非都是我们提到的这两个示例的变种，类模板也不例外。

## 类模板偏特化

```cpp
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!
```

稍微提一下类外的写法，不过其实**通常不推荐写到类外**，目前还好；很多情况涉及大量模板的时候，类内声明写到类外非常的麻烦。

***

我们再举一个偏特化类模板中的类模板，全特化和偏特化一起使用的示例：

```cpp
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 [通过编译](https://godbolt.org/z/rvYhf9K6M)，这是**编译器 BUG 需要注意**。

语法形式是简单的，不做过多的介绍。

其实和全特化没啥区别。

## 实现 `std::is_same_v`

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

```cpp
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` 一起使用，这会在我们后续的章节进行讲解，不用着急。

如还有需求，查看 [cppreference](https://zh.cppreference.com/w/cpp/language/partial_specialization)。

最后强调一句：***函数模板没有偏特化，只有类模板和变量模板可以***。
