參考:IBM編譯器中國開發團隊博客
其中幾個比較好的例子,下面的class 都可以換成 typename,向後兼容性比較好。
而且typename是較class更加新的標準,具體class 可能導致的問題可見這篇文章,講的特別詳細
知無涯值C++ typename
struct t1{}; struct t2{}; struct t3{};
void func(t1 arg){ printf("called t1\n"); }
void func(t2 arg){ printf("called t2\n"); }
void func(t3 arg){ printf("called t3\n"); }
int main(void)
{
t1 x1; t2 x2; t3 x3;
func(x1);
func(x2);
func(x3);
return 0;
}
輸出:
called t1
called t2
called t3
這個很簡單,編譯器根據傳遞給函數的實參類型來決定調用哪個函數,這就是重載解析。在調用前,編譯器有一個候選函數調用列表:
void func(t1);
void func(t2);
void func(t3);
每個調用函數都有各自的參數,編譯器根據參數最匹配原則選擇相應的函數
模板函數:
#include <iostream>
#include <typeinfo>
struct t1{}; struct t2{}; struct t3{};
using namespace std;
template <class A, class B, class C> void func(A a1, B a2, C a3)
{
cout << "A: " << typeid(a1).name() << endl;
cout << "B: " << typeid(a2).name() << endl;
cout << "C: " << typeid(a3).name() << endl;
}
int main(void)
{
t1 x1; t2 x2; t3 x3;
func(x1,x2,x3);
return 0;
}
輸出:
A: t1
B: t2
C: t3
在這個使用了一個函數模板的例子中,編譯器有一個帶有3個未知類型<A,B,C>的候選調用函數,它將實參 (x1,x2,x3)傳遞給函數func中的3個形參(A,B,C),可以很容易看到編譯器是如何推導出模板參數的:
A t1
B t2
C t3
編譯器實例化了模板函數;將實參傳遞給模板函數中的形參以創建一個真正的函數:
void func(t1 a1, t2 a2, t3 a3)
如果有其他的候選重載函數,他們都將會和非模板函數的例子一樣被綁定在一起,然後在重載解析中根據實參類型調用相應的函數。
重載解析允許用戶創建同一個函數的不同版本,這些函數將根據傳進來的參數的類型,做一些不同的操作。編譯器會根據類型信息來選擇相應的函數。通過使用模板函數,用戶可以定義帶參數化類型的函數,從而減少需要定義的重載函數的個數。編譯器會選擇正確的模板併爲用戶創建候選的重載函數。
類模板:
#include <iostream>
#include <typeinfo>
using namespace std;
struct t1{}; struct t2{}; struct t3{};
template <class A, int I> struct container{
void callMe(){
cout << "primary A: " << typeid(A).name() << " I: " << I << endl;
}
};
int main(void)
{
container<t1,10> test;
test.callMe();
return 0;
}
輸出:
primary A: t1 I: 10
在這個例子中,編譯器並不會玩什麼把戲,這個例子中只有一個類container, 它接收了實參<t1,10>並傳遞給模板參數<A, I>,推導出A即爲t1,I爲10。
含有一個全特化的類模板:
#include <iostream>
#include <typeinfo>
using namespace std;
struct t1{}; struct t2{}; struct t3{};
template <class A, int I> struct container{
void callMe(){
cout << "primary A: " << typeid(A).name() << " I: " << I << endl;
}
};
template <> struct container<t3,99>{
void callMe(){
cout << "complete specialization t3, 99" << endl;
}
};
int main(void)
{
container<t1,10> test1;
test1.callMe();
container<t3,99> test2;
test2.callMe();
return 0;
}
輸出:
primary A: t1 I: 10
complete specialization t3, 99
在這個例子中有兩個模板,其中一個是全特化模板,即模板中模板參數全部指定爲確定的類型。特化(specialized)不過是一個花哨的術語,意思是形參不再爲形參,它們已經有了確定的值。我更傾向於使用“全特化”這個術語,感覺這更容易讓人理解。但是在大多數的C++書籍,包括標準C++,都將其稱爲“顯示特化”。
現在編譯器有了兩個類名都爲container的類模板,類模板被重載:
template <class A, int I> struct container;
template <> struct container<t3,99>;
當編譯器執行到container<t1,10>test1, 對於參數<t1, 10>:
- 候選模板1可推出 <A=t1, I=10> ,所以候選模板1有效;
- -候選模板2無法推出<t3,99> 能與 <t1,10>匹配,所以候選模板2被剔除。
這樣編譯器只有一個候選模板1,也即最終被匹配的模板。
當編譯器執行到container<t3, 99>test2,對於參數<t3, 99>: - 候選模板1可推出<A=t3, I=99>,所以候選模板1有效
- 候選模板2,很明顯 <t3,99> 與模板中的 <t3,99>相匹配,所以候選模板2有效。
當在一個程序中發現有兩個或者兩個以上候選模板有效時,編譯器根據最匹配原則選擇最爲匹配的那個模板,即候選模板2。
偏特化+全特化的一個例子:
#include <iostream>
#include <typeinfo>
using namespace std;
struct t1{}; struct t2{}; struct t3{};
template <class A, int I> struct container{
void callMe(){
cout << "primary A: " << typeid(A).name() << " I: " << I << endl;
}
};
template <class A1> struct container<A1,25>{
void callMe(){
cout << "partial specialization" << typeid(A1).name() << " and 25 " << endl;
}
};
template <> struct container<t3,99>{
void callMe(){
cout << "complete specialization t3, 99" << endl;
}
};
int main(void)
{
container<t1,10> test1;
test1.callMe();
container<t3,99> test2;
test2.callMe();
container<t2,25> test3;
test3.callMe();
container<t3,25> test4;
test4.callMe();
return 0;
}
輸出:
primary A: t1 I: 10
complete specialization t3, 99
partial specializationt2 and 25
partial specializationt3 and 25
此例有3個候選模板:
template <class A, int I> struct container;
template struct container<A1,25>;
template <> struct container<t3,99>;
模板1是帶有兩個模板參數的主模板,模板2是帶有一個模板參數的偏特化模板,模板3是無模板參數的全特化模板。
如前面所說,偏特化也僅是一個花哨的術語,偏特化模板中的模板參數沒有被全部確定,需要編譯器在編譯時進行確定。
當編譯器編譯執行到container<t3,25> test4,參數爲<t3,25>:
- 候選模板1,編譯器可推導出 <A=t3, I=25>,故候選模板1有效;
- 候選模板2,編譯器爲偏特化模板可推導出<A1=t3, 25>,故候選模板2有效;
- 候選模板3, 編譯器不可能從<t3,25>得到<t3,99>,故候選模板3被剔除。
候選模板2是最匹配的模板,故匹配模板2。
最後一個例子:
#include <iostream>
#include <typeinfo>
using namespace std;
struct t1{}; struct t2{}; struct t3{};
template <class A, int I> struct container{
void callMe(){
cout << "primary A: " << typeid(A).name() << " I: " << I << endl;
}
};
template <int I1> struct container<t3,I1>{
void callMe(){
cout << "partial specialization t3 and " << I1 << endl;
}
};
template <> struct container<t3,99>{
void callMe(){
cout << "complete specialization t3, 99 " << endl;
}
};
int main(void)
{
container<t1,10> test1;
test1.callMe();
container<t3,99> test2;
test2.callMe();
container<t3,75> test3;
test3.callMe();
container<t3,25> test4;
test4.callMe();
return 0;
}
輸出:
primary A: t1 I: 10
complete specialization t3, 99
partial specialization t3 and 75
partial specialization t3 and 25
本質上,偏特化模板的匹配和選擇過程與重載解析非常類似。實際上,在非常複雜的偏特化情況下,編譯器可能就是將偏特化直接譯成函數,然後直接調用重載解析來處理。
重載解析和偏特化匹配都用到了模板參數推導。