转:https://www.ibm.com/developerworks/community/blogs/12bb75c9-dfec-42f5-8b55-b669cc56ad76/entry/c__e6_a8_a1_e6_9d_bf__e7_a9_b6_e7_ab_9f_e4_bb_80_e4_b9_88_e6_98_af_e7_89_b9_e5_8c_96?lang=en
我
已经制定 了一个计 划,希望 可以定期 地在博客 上更新文 章。我将 探讨编译 器如何处理模板等这类有意思的事情,并佐以实例。
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; }
#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; }
在这个例子中,编译器并不会玩什么把戏,这个例子中只有一个类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; }
在这个例子中有两个模板,其中一个是全特化模板,即模板中模板参数全部指定为确定的类型。特化(specialized)不过是一个花哨的术语,意思是形参不再为形参,它们已经有了确定的值。我更倾向于使用“全特化”这个术语,感觉这更容易让人理解。但是在大多数的C++书籍,包括标准C++,都将其称为“显示特化”。
现在编译器有了两个类名都为container的类模板,类模板被重载:
- 候选模板1可推出 <A=t1, I=10> ,所以候选模板1有效;
现在编译器有了两个类名都为container的类模板,类模板被重载:
- template <
class A, int I> struct container; -
template < > struct container< t3,99> ;
- 候选模板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有效
当编译器执行到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; }
-
template < class A, int I> struct container; -
template < class A1> 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被剔除。
当编译器编译执行到container<
- 候选模板1,编译器可推导出 <
- 候选模板2,编译器为偏特化模板可推导出<
- 候选模板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; }
本质上,偏特化模板的匹配和选择过程与重载解析非常类似。实际上,在非常复杂的偏特化情况下,编译器可能就是将偏特化直接译成函数,然后直接调用重载解析来处理。
重载解析和偏特化匹配都用到了模板参数推导。
重载解析和偏特化匹配都用到了模板参数推导。