模板的特化(Specialisation
)分為:
-
全特化(
Full Specialisation
或Explicit Specialisation
) -
部分特化(
Partial Specialisation
)
其中,部分特化一般被翻譯為偏特化,但我認為部分特化是個和全特化對照的名字,更容易理解。
全特化與部分特華之間的區別非常簡單:如果特化后的版本,
- 不再有模板參數,就是全特化;
- 仍然有模板參數,就是部分特化。
比如:
template <typename T>
struct Foo; // 基礎模板
template <>
struct Foo<double>; // 全特化
template <typename T>
struct Foo<T*>; // 部分特化
template <typename T1, typename T2>
struct Foo<void (T1, T2)>; // 部分特化
template <typename T, typename R>
struct Bar; // 基礎模板
template <>
struct Bar<double, int>; // 全特化
template <typename T>
struct Bar<T, int>; // 部分特化
函數模板的特化
部分特化是類模板的專利。函數模板對此特性并不支持,它只支持全特化。
這是因為C++
支持函數重載,連模板函數也可以重載,通過它就基本可以達到部分特化的效果。比如:
template <typename T1, typename T2>
void f(T1, T2); // 基礎模板f
template <typename T>
void f(T1, int); // 重載:另一個基礎模板f,而不是部分特化
template <typename T>
void g(T1); // 基礎模板 g
template <typename T>
void g(T1*) // 重載:另一個基礎模板g,而不是部分特化
避免特化函數模板
事實上,對于函數模板來說,即便是全特化,也最好不要使用。取而代之,你應該使用函數重載。
template <typename T>
T min(T, T); // 基礎模板 min
int min(int, int); // 使用函數重載(這是一個普通函數)
template <>
int min<int>(int, int); // 函數模板特化: DON'T DO THIS!
正如你看到的,模板特化的語法形式更加復雜,但這不是不使用函數模板特化的主要原因。
主要原因在于,和函數重載比較起來,特化后的模板函數屬于二等公民。
簡單的說,當編譯器對一個函數調用選擇其合適的實現時,優先級從高到低依次為:
- 普通函數
- 基礎模板
- 特化模板
一等公民
普通函數和基礎模板都屬于函數重載,C++
將它們都看作一等公民。
當對一個函數調用選擇實現時,C++
會從它們中間選擇一個最匹配的版本。比如:
template <typename T>
void f(T, double); // (a)
void f(int, int); // (b)
f(5, 2.0); // 選擇 (a)
f(5, 10); // 選擇 (b)
當普通函數和基礎模板的選擇出現二義性時,普通函數會被優先選擇。因為C++
總是選擇最特殊的版本(most specialized
):
template <typename T>
T min(T, T); // (a)
int min(int, int); // (b)
min(5, 10); // 語義上兩者都匹配,選擇(b)
另外,普通函數相對于模板函數要更有彈性:普通函數支持類型的自動轉化;但模板函數則更刻板,不能發現類型之間的自動轉換關系。
template <typename T>
T min(T, T);
min(5, (short)10); // 編譯出錯
int min(int, int);
min(5, (short)10); // 編譯成功
在這種情況下,當一個調用需要在兩者之間作出選擇時,普通函數會是獲勝者。
template <typename T>
T min(T, T); // (a)
int min(int, int); // (b)
min(5, (short)10); // 選擇 (b)
總結起來,作為一等公民,所有的重載函數——無論是普通函數還是基礎模板函數——都被作為第一等級的候選對象;C++
編譯器會在它們之間找到那個最為匹配的版本。
如果出現二義性,則優先選擇普通函數。
二等公民
如果最匹配的版本是基礎模板函數,編譯器才會去查看它名下的特化版本。如果它們之中存在一個比基礎模板函數更匹配的版本,才會選擇這個特化版本。這正是為何將模板特化函數稱為二等公民的原因。

而下面這個例子則反映了模板特化函數的二等公民地位:
template <typename T>
void f(T); // (a): 基礎模板
template<>
void f(int*); // (b): (a)的特化版本
template <typename T>
void f(T*); // (c): 基礎模板,重載
int a = 10;
f(&a); // 選擇 (c): 盡管 (b) 更匹配,但它是二等公民
一旦將其改為普通重載函數,就可以選擇最匹配的函數:
template <typename T>
void f(T); // (a): 基礎模板
void f(int*); // (b): 普通函數,重載
template <typename T>
void f(T*); // (c): 基礎模板,重載
int a = 10;
f(&a); // 選擇 (b)
小結
本文主要介紹了函數模版特化的性質。在下篇文章里,會介紹模版特化的另一個類別:類模版特化。