# 模板偏特化

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

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

比如：指针类型，这是一类类型，有 `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)。

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mq-bai.gitbook.io/modern-cpp-templates-cookbook/05-mu-ban-pian-te-hua.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
