> 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/04-mu-ban-quan-te-hua.md).

# 模板全特化

本节将介绍模板全特化。

其实很多东西都能进行全特化，不过我们围绕着之前的内容：函数模板、类模板、变量模板来展开。

## 函数模板全特化

给出这样一个函数模板 `f`，你可以看到，它的逻辑是返回两个对象相加的结果，那么如果我有一个需求：“**如果我传的是一个 double 一个 int 类型，那么就让它们返回相减的结果**”。

```cpp
template<typename T,typename T2>
auto f(const T& a, const T2& b)  {
    return a + b;
}
```

> C++14 允许函数返回声明的 auto 占位符自行推导类型。

这种定制的需求很常见，此时我们就需要使用到模板全特化：

```cpp
template<>
auto f(const double& a, const int& b){
    return a - b;
}
```

> 当特化函数模板时，如果模板实参推导能通过函数实参提供，那么就**可以忽略它的模板实参**。

语法很简单，只需要先写一个 `template<>` 后面再实现这个函数即可。

***

不过我们其实有两种写法的，比如上面那个示例，我们还可以写明模板实参。

```cpp
template<>
auto f<double, int>(const double& a, const int& b) {
    return a - b;
}
```

个人建议写明更加明确，因为很多时候模板实参只是函数形参类型的**一部分**而已，比如上面的 `const double&`、`const int&` 只有 `double` 、`int` 是模板实参。

[使用](https://godbolt.org/z/G9bzsTP5a)：

```cpp
std::cout << f(2, 1) << '\n';    // 3
std::cout << f(2.1, 1) << '\n';  // 1.1
```

## 类模板全特化

和函数模板一样，类模板一样可以进行全特化。

```cpp
template<typename T> // 主模板
struct is_void{
    static constexpr bool value = false;
};
template<>           // 对 T = void 的显式特化
struct is_void<void>{
    static constexpr bool value = true;
};

int main(){
    std::cout <<std::boolalpha<< is_void<char>::value << '\n';    // false
    std::cout << std::boolalpha << is_void<void>::value << '\n';  // true
}
```

我们使用全特化，实现了一个 `is_void` 判断模板类型实参是不是 `void` 类型。

虽然很简单，但我们还是稍微强调一下：同一个类模板实例化的不同的类，彼此之间毫无关系，而静态数据成员是属于类的，而不是模板类；模板类实例化的不同的类，它们的静态数据成员不是同一个，请注意。

***

我们知道标准库在 `C++17` 引入了 `is_xxx` 的 `_v` 的版本，就不需要再写 `::value` 了。所以我们也可以这么做，这会使用到变量模板。

```cpp
#include <iostream>

template<typename T> // 主模板
struct is_void{
    static constexpr bool value = false;
};
template<>           // 对 T = void 的显式特化
struct is_void<void>{
    static constexpr bool value = true;
};

template<typename T>
constexpr bool is_void_v = is_void<T>::value;

int main(){
    std::cout <<std::boolalpha<< is_void_v<char> << '\n';    // false
    std::cout << std::boolalpha << is_void_v<void> << '\n';  // true
}
```

***

我们再给出一个简单的示例：

```cpp
template<typename T>
struct X{
    void f()const{
        puts("f");
    }
};

template<>
struct X<int>{
    void f()const{
        puts("X<int>");
    }
    void f2()const{}

    int n;
};

int main(){
    X<void> x;
    X<int> x_i;
    x.f();         // 打印 f
    //x.f2();      // Error!
    x_i.f();       // 打印 X<int>
    x_i.f2();
}
```

我们要明白，写一个类的全特化，就相当于写一个新的类一样，你可以自己定义任何东西，不管是函数、数据成员、静态数据成员，等等；根据自己的需求。

## 变量模板全特化

```cpp
#include <iostream>

template<typename T>
constexpr const char* s = "??";

template<>
constexpr const char* s<void> = "void";

template<>
constexpr const char* s<int> = "int";

int main(){
    std::cout << s<void> << '\n'; // void
    std::cout << s<int> << '\n';  // int
    std::cout << s<char> << '\n'; // ??
}
```

语法形式和前面函数模板、类模板都类似，很简单，这个变量模板是类型形参。我们特化了变量模板 `s` 的模板实参为 `void` 与 `int` 的情况，修改 `s` 的初始化器，让它的值不同。

```cpp
template<typename T>
constexpr bool is_void_v = false;

template<>
constexpr bool is_void_v<void> = true;

int main(){
    std::cout << std::boolalpha << is_void_v<char> << '\n';   // false
    std::cout << std::boolalpha << is_void_v<void> << '\n';   // true
}
```

上面的变量模板，模板是类型形参，我们根据类型进行全特化。我们特化了 `is_void_v` 的模板实参为 `void` 的情况，让 `is_void_v` 值 为 `true`。

## 细节

前面函数、类、变量模板的全特化都讲的很简单，示例也很简单，或者说语法本身大多数时候就是简单的。我们在这里进行一些更多的**补充一些细节**。

***

**特化必须在导致隐式实例化的首次使用之前**，在每个发生这种使用的翻译单元中声明：

```cpp
template<typename T> // 主模板
void f(const T&){}

void f2(){
    f(1);  // 使用模板 f() 隐式实例化 f<int>
}

template<> // 错误 f<int> 的显式特化在隐式实例化之后出现
void f<int>(const int&){}
```

如果 f2 中的调用换成 `f(1.)` 就[没问题](https://godbolt.org/z/WrM73Pr7f)，它隐式实例化的就是 `f<double>` 了。

***

只有声明没有定义的模板特化可以像其他不完整类型一样使用（例如可以使用到它的指针和引用）：

```cpp
template<class T> // 主模板
class X;
template<>        // 特化（声明，不定义）
class X<int>;
 
X<int>* p;       // OK：指向不完整类型的指针
X<int> x;        // 错误：不完整类型的对象
```

***

函数模板和变量模板的显式特化是否为 [inline](https://zh.cppreference.com/w/cpp/language/inline)/[constexpr](https://zh.cppreference.com/w/cpp/language/constexpr)/[constinit](https://zh.cppreference.com/w/cpp/language/constinit)/[consteval](https://zh.cppreference.com/w/cpp/language/consteval) **只与显式特化自身有关**，**主模板的声明是否带有对应说明符对它没有影响**。模板声明中出现的[属性](https://zh.cppreference.com/w/cpp/language/attributes)在它的显式特化中也没有效果：

```cpp
template<typename T>
int f(T) { return 6; }
template<>
constexpr int f<int>(int) { return 6; }   // OK，f<int> 是以 constexpr 修饰的

template<class T>
constexpr T g(T) { return 6; }            // 这里声明的 constexpr 修饰函数模板是无效的
template<>
int g<int>(int) { return 6; }             //OK，g<int> 不是以 constexpr 修饰的

int main(){
    constexpr auto n = f<int>(0);         // OK，f<int> 是以 constexpr 修饰的，可以编译期求值
    //constexpr auto n2 = f<double>(0);   // Error! f<double> 不可编译期求值

    //constexpr auto n3 = g<int>(0);      // Error! 函数模板 g<int> 不可编译期求值

    constexpr auto n4 = g<double>(0);     // OK! 函数模板 g<double> 可编译期求值
}
```

[可通过编译](https://godbolt.org/z/67Wevdoch)。

如果主模板有 constexpr 属性，那么模板实例化的，如 `g<double>` 自然也是附带了 `constexpr`，但&#x662F;***如果其特化没有，那么以特化为准***（如 `g<int>`）。

***

## 特化的成员

特化成员的写法略显繁杂，但是只要明白其逻辑，一切就会很简单。

> **主模板**

```cpp
template<typename T>
struct A{
    struct B {};      // 成员类

    template<class U> // 成员类模板
    struct C {};
};
```

> **特化模板类**。`A<void>`。

```cpp
template<>
struct A<void>{
    void f();       // 类内声明
};

void A<void>::f(){  // 类外定义
    // todo..
}
```

> **特化成员类**。设置 `A<char>` 的情况下 `B` 类的定义。

```cpp
template<>              
struct A<char>::B{      // 特化 A<char>::B
    void f();           // 类内声明
};

void A<char>::B::f(){   // 类外定义
    // todo..    
}
```

> **特化成员类模板**。设置 `A<int>` 情况下模板类 `C` 的定义。

```cpp
template<>
template<class U>
struct A<int>::C{
    void f();               // 类内声明
};
// template<> 会用于定义被特化为类模板的显式特化的成员类模板的成员
template<>
template<class U>
void A<int>::C<U>::f(){     // 类外定义
    // todo..
}
```

***

> **特化类的成员函数模板**

其实语法和普通特化函数模板没什么区别，类外的话那就指明函数模板是在 X 类中。

```cpp
struct X{
    template<typename T>
    void f(T){}

    template<>              // 类内特化
    void f<int>(int){
        std::puts("int");
    }
};

template<>                  // 类外特化
void X::f<double>(double){
    std::puts("void");
}

X x;
x.f(1);     // int
x.f(1.2);   // double
x.f("");    
```

***

> **特化类模板的成员函数模板**

成员或成员模板可以在多个外围类模板内嵌套。在这种成员的显式特化中，对每个显式特化的外围类模板都有一个 **`template<>`**。

其实就是注意有几层那就多套几个 `template<>`，并且指明模板类的模板实参。下面这样：就是自定义了 `X<void>` 且 `f<double>` 的情况下的函数。

```cpp
template<typename T>
struct X {
    template<typename T2>
    void f(T2) {}

    template<>
    void f<int>(int) {            // 类内特化，对于 函数模板 f<int> 的情况
        std::puts("f<int>(int)"); 
    }
};

template<>
template<>
void X<void>::f<double>(double) { // 类外特化，对于 X<void>::f<double> 的情况
    std::puts("X<void>::f<double>");
}

X<void> x;
x.f(1);    // f<int>(int)
x.f(1.2);  // X<void>::f<double>
x.f("");
```

> 视频中的代码，模板类和成员函数模板都用的 ***`T`***，只能在 `msvc` 下运行，gcc 与 clang 有歧义，**需要注意**。

> **类内对成员函数 `f` 的特化**，在 `gcc` [**无法通过编译**](https://godbolt.org/z/qYGjGhhPE)，根据考察，这是一个很多年前就有的 `BUG`，使用 `gcc` 的开发者自行注意。

## 总结

我们省略了一些内容，但是以上在我看来也完全足够各位学习使用了。如有需求，查看 [cppreference](https://zh.cppreference.com/w/cpp/language/template_specialization)。

模板全特化的语法主要核心在于 `template<>`，以及你需要注意，你到底要写几个 `template<>`。其他的都很简单。
