参考: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
本质上,偏特化模板的匹配和选择过程与重载解析非常类似。实际上,在非常复杂的偏特化情况下,编译器可能就是将偏特化直接译成函数,然后直接调用重载解析来处理。
重载解析和偏特化匹配都用到了模板参数推导。
来源:CSDN
作者:lonelyisland_syz
链接:https://blog.csdn.net/lonelyisland_syz/article/details/103463887