上文函数模版初探介绍了函数模版的基本使用,本文深入探讨函数模版。
函数模版参数的类型转换
函数模版有多个同类型的形参,使用过程中会碰到类型转换问题。
下面是一个加法函数模版:
template <typename T>
T add2(T a, T b) {
std::cout << "T add2(T a, T b)"
<< std::endl;
return a + b;
}
使用隐式实例化,2个实参的类型必须相同,否则编译出错No matching function for call to add
。
使用显示实例化,2个实参的类型可以不同,但是会发生隐式类型转换。
void test1() {
// 2个实参类型相同,可以使用隐式实例化
int a1 = 1;
int b1 = 2;
add2(a1, b1);
// 2个实参类型不同,不能使用隐式实例化
int a2 = 1;
short b2 = 1;
// No matching function for call to 'add2'
// add2(a2, b2);
// 显示实例化, short被隐式转换成int
add2<int>(a2, b2);
}
函数模版重载
函数模版跟普通函数一样支持重载。函数参数列表中,参数不一定是泛型,也可以是具体的类型。
比如,加法函数模版中,增加3个数相加的函数模版。第3个参数,可以是泛型,也可以是具体的double
类型。
template <typename T>
T add2(T a, T b, T c) {
std::cout << "T add2(T a, T b, T c)"
<< std::endl;
return a + b + c;
}
template <typename T>
T add2(T a, T b, double c) {
std::cout << "T add2(T a, T b, double c)"
<< std::endl;
return a + b + c;
}
函数模版的局限性及解决方案
函数模版很可能无法处理某些类型。
比如下面的例子,如果T
是自定义类,则无法正常运行。
template <typename T>
bool equal(T &a, T &b) {
return a == b;
}
class FTPerson {
public:
int age;
};
一种解决方案是,为特定类型提供具体化的函数定义,这称之为显示具体化(explicit specialization
)。
显示具体化的原型和定义:
template<> bool equal<FTPerson>(FTPerson &p1, FTPerson &p2);
template<> bool equal<FTPerson>(FTPerson &p1, FTPerson &p2) {
std::cout << "equal(FTPerson &p1, FTPerson &p2)"
<< std::endl;
return p1.age == p2.age;
}
编译器会选择最合适的显示具体化函数。
void test3() {
FTPerson p1;
FTPerson p2;
equal(p1, p2); // 调用的是具体化函数
}
编译器选择哪个函数
调用函数时,如果有多个函数或者模版符合要求:
- 函数名称相同
- 实参与形参个数相同,并且类型相同或者可以隐式转换
编译器必须选择一个最佳的函数。以下匹配规则,优先级从高到低:
- 函数完全匹配,按照下面优先级匹配
- 普通函数
- 模版的显示具体化函数
- 函数模版
- 提升转换,比如
char
转换为int
,float
转换为double
- 标准转换,比如
char
转换为float
,int
转换为char
,long
转换为double
下面是5个函数或者模版,编号是1到5。使用不同编号的函数和测试用例,验证匹配规则。
// #1 普通函数
void eat(int a) {
std::cout << "eat(int a)"
<< std::endl;
}
// #2 普通函数
void eat(char a) {
std::cout << "eat(char a))"
<< std::endl;
}
// #3 普通函数
void eat(float a) {
std::cout << "eat(float a)"
<< std::endl;
}
// #4 函数模版
template<typename T> void eat(T a) {
std::cout << "template void eat(T a)"
<< std::endl;
}
// #5 模版的显示具体化函数
template<> void eat(int a) {
std::cout << "template void eat(int a)"
<< std::endl;
}
测试用例1:
void test4() {
int a = 1;
eat(a);
}
下面情况都是完全匹配,输出结果如下:
- 使用
#1
,#4
,#5
,则编译器选择普通函数#1
,输出日志eat(int a)
- 使用
#4
,#5
,则编译器选择显示具体化函数#5
,输出日志template void eat(int a)
下面情况是完全匹配优先级高于提升转换或者标准转换,输出结果如下:
- 使用
#2
,#3
,#4
,则编译器选择函数模版#4
,输出日志template void eat(T a)
测试用例2:
void test5() {
char a = 'a';
eat(a);
}
下面情况是提升转换优先级高于标准转换,输出结果如下:
- 使用
#1
,#3
,则编译器选择提升转换#1
,输出日志eat(int a)
总结
- 使用函数模版,要注意隐式类型转换
- 函数模版支持重载
- 函数模版不能处理所有的类型,可以为特定类型提供具体化的函数定义
- 如果多个函数或者模版符合要求,编译器会根据优先级,选择最合适的函数