Template 特化

来源:互联网 发布:ae2015 mac破解补丁 编辑:程序博客网 时间:2024/06/05 22:12
转: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



这个很简单,编译器根据传递给函数的实参类型来决定调用哪个函数,这就是重载解析。在调用前,编译器有一个候选函数调用列表:
  1. void func(t1);
  2. void func(t2);
  3. 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的类模板,类模板被重载:
  1. template <class A, int I> struct container;
  2. 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个候选模板:
  1. template <class A, int I> struct container;
  2. template <class A1> struct container<A1,25>;
  3. 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


 
本质上,偏特化模板的匹配和选择过程与重载解析非常类似。实际上,在非常复杂的偏特化情况下,编译器可能就是将偏特化直接译成函数,然后直接调用重载解析来处理。
重载解析和偏特化匹配都用到了模板参数推导。
0 0
原创粉丝点击