模板偏特化

模板偏特化是模板特化的一种特定形式。它通常用于C++程序设计语言，允许程序员只特化模板的部分模板参数；与之相对的是显式全特化，即提供所有的模板参数。

## 模板与特化

类模板本质上是元类（meta-classe）：它们是部分抽象的数据类型，为编译器提供指令，指导编译器如何生成带有正确数据成员的类。例如，C++ 标准库容器都是类模板。当程序员使用std::vector时，需要用特定的数据类型对其进行实例化，例如int、string 或 double。每种类型的std::vector 都会在编译器的目标代码中生成一个不同的类，每个类处理不同的数据类型。这个过程被称为泛型的单态化（monomorphization）。

如果开发者知道某个类模板会频繁地与某种特定数据类型搭配使用，并且该数据类型支持某些优化（例如对整数使用位运算替代乘或除以2），那么可以为该模板预设部分模板参数，从而定义一个特化的类模板。当编译器在代码中遇到该类模板的实例化时，通常会选择最特化、最匹配的模板定义。因此，如果所有模板参数都匹配，显式全特化（指定了全部模板参数）的优先级会高于偏特化。

## 偏特化

只有类模板和变量模板（C++14起）有偏特化。函数模板不能偏特化，只能重载或全特化。

### 偏特化语法

```cpp
//主模板：
template <主模板形参列表>
class-key template-name{
    // ...
};

//偏特化模板：
template <偏特化自己需要的形参>
class-key 主模板名<一一对应主模板形参列表的偏特化实参模式> {
};

//全特化模板：
template <> //模板参数列表为空
class-key主模板名<一一对应主模板形参列表的全特化实参模式> {
};
```

### 偏特化匹配优先级

如果多个偏特化都能匹配，编译器会选择 最特化 的那个。如果多个偏特化版本之间无法决定，会报二义性错误。

```cpp
#include <iostream>

template <typename T>
struct X {
    static constexpr const char* name = "primary";
};

template <typename T>
struct X<T*> {
    static constexpr const char* name = "pointer";
};

template <typename T>
struct X<const T*> {
    static constexpr const char* name = "pointer to const";
};

int main() {
    std::cout << X<int>::name << '\n';        // primary
    std::cout << X<int*>::name << '\n';       // pointer
    std::cout << X<const int*>::name << '\n'; // pointer to const
}
```

## 示例

假设有一个带两个模板参数的 `KeyValuePair`类：

```cpp
template <typename K, typename V>
class KeyValuePair {};
```

下面是`KeyValuePair`显式全特化的例子，将整型与字符串配对，类名与原模板保持一致：

```cpp
using std::string;

template <>
class KeyValuePair<int, string> {};
```

下面是`KeyValuePair`偏特化的例子，类名不变，只特化一个模板参数：

```cpp
template <typename K>
class KeyValuePair<K, string> {};
```

下例中的 `KeyStringPair`继承自原模板并使用新名称，同样定义了偏特化。与上面的显式特化不同，它只特化了基类的模板参数`V`，而`K`仍然保持泛型：

```cpp
template <typename K>
class KeyStringPair : public KeyValuePair<K, string> {};
```

特化哪些参数、保留哪些参数为泛型并无限制。例如，下面同样是`KeyValuePair` 合法的偏特化：

```cpp
template <typename V>
class IntegerValuePair : public KeyValuePair<int, V> {};
```

## 注意事项

C++模板不仅限于类，也可用于定义函数模板、变量模板。尽管函数模板可以全特化，但不能进行偏特化—— 无论它是成员函数模板还是非成员函数模板。这对编译器实现者而言更友好，但会影响开发者使用的灵活性与粒度。不过，函数模板支持重载，其效果几乎等价于函数模板偏特化。示例如下：

```cpp
import std;
using std::string;

// 合法：基础函数模板
template <typename Ret, typename Arg>
Ret Foo(Arg arg);

// 合法：显式/全特化
template <>
string Foo<string, char>(char arg) {
    return "Full";
}

// 非法：函数模板不能偏特化
// template <typename Arg>
// void Foo<void, Arg>(Arg arg);

// 合法：这不是函数模板偏特化，而是函数模板重载，即这是不同的函数模板声明/定义。匹配指针类型
template <typename Ret, typename Arg>
Ret Foo(Arg* argPtr) {
    return "PtrOverload";
}

// 合法但不符合标准：非可重载声明
template <typename Arg>
string Foo(Arg arg) {
    return "Return1";
}

// 合法但不符合标准：非可重载声明
template <typename Ret>
Ret Foo(char arg) {
    return "Return2";
}
```

在上例中，最后两个`Foo`虽然语法合法，但根据 C++ 标准属于病态形式（ill-formed），因为它们属于不可重载的声明。原因是：函数重载只看函数名、参数列表、命名空间，不看返回值。不过，仍然可以通过显式指定签名的方式调用这些函数：

```cpp
import std;

using std::string;

int main(int argc, char* argv[]) {
    char c = 'c';
    // let the compiler resolve the call
    string r0 = Foo(c);
    // explicitly specify which function to call
    string r1 = Foo<string>(c);
    string r2 = Foo<string, char>(c);
    string r3 = Foo<string, char>(&c);

    // generate output:
    // Return1, Return2, Full, PtrOverload
    std::println("{}, {}, {}, {}", r0, r1, r2, r3);
}
```

实际上，很多情况下函数模板显式特化并不推荐，原因是：

- 它不参与普通重载那样的直觉选择
- 很容易让代码可读性变差
- 常常还不如直接加一个非模板重载更清晰

例如通常更推荐：

```cpp
void f(int) {
    std::cout << "int\n";
}

template<class T>
void f(T) {
    std::cout << "general\n";
}
```

而不是特意写显式特化。