模板偏特化

模板偏特化是模板特化的一種特定形式。它通常用於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";
}
```

而不是特意寫顯式特化。