函数模板
函数模板本身并不是函数,是将本来应该我们做的事情交给编译器去做。
两种调用方式:(1)自动推导 (2)显示调用
template<typename T> //声明一个模板,告诉编译器之后的T为通用类型
T add(T a, T b) {
return a + b;
}
int main() {
cout << add(3, 4) << endl;
}
/*
在编译阶段编译器会根据实际情况,通过类模板实例化出:
int add(int a, int b) {
return a + b;
}
所以说程序调用的是int add(int, int)这个函数,并不是函数模板
可以用命令: 【nm -C 可执行文件】 查看生成的函数信息
例如查看以上代码则会有: 0000000000001250 W int add<int>(int, int)
其中<int>代表这是由类模板生成的函数
注意:
此种情况下传入的参数3,4都是int,在模板推导过程中不存在冲突现象,所以正确生成了函数实例
*/
注意:
- 普通函数与模板函数共存时,如果普通函数的参数类型可以完全匹配,则执行普通函数,不进行函数模板的实例化,顾名思义就是吃现有的饭,不去考虑还没有做好的饭。
- 如果普通函数和模板函数共存,普通函数参数类型不完全匹配,而实例化函数可以完全匹配,则进行模板的实例化。
- 若普通函数和模板函数共存,指定了需要实例化
Add<int>(a,b)
,则进行实例化。 - 模板必须要确定出T的数据类型,才可以使用
template<typename T>
void func() {
cout << "func" << endl;
}
int main() {
func<int>(); //此时函数模板通过参数无法进行类型推导,所以必须显示调用
return 0;
}
解决推导过程中发生参数冲突的情况
- 显示调用函数模板
template<typename T>
T add(T a, T b) {
return a + b;
}
int main() {
cout << add(3, 4) << endl; //7
cout << add(3.3, 4.5) << endl; //7.8
//cout << add(3, 4.1) << endl; //error
cout << add<int>(3, 4.1) << endl; //7
cout << add<double>(3, 4.1) << endl; //7.1
return 0;
}
- 多抽象出一个类型
template<typename T, typename U>
T add(T a, U b) {
return a + b;
}
int main() {
cout << add(3, 4) << endl; //7
cout << add(3.3, 4.5) << endl; //7.8
cout << add(3, 4.5) << endl; //7 here
cout << add(4.5, 3) << endl; //7.5
return 0;
}
注意:此时出现一个新的问题——无法确定函数模板的返回值类型! 所以要引入decltype
来解决
template<typename T, typename U>
decltype(T() + U()) add(T a, U b) {
return a + b;
}
int main() {
cout << add(3, 4) << endl; //7
cout << add(3.3, 4.5) << endl; //7.8
cout << add(3, 4.5) << endl; //7.5 here
cout << add(4.5, 3) << endl; //7.5
return 0;
}
注意:此时如果a、b
是自定义数据类型的话,且没有构造函数,那么这段代码还是存在BUG,所以还要引入返回值后置来解决
内建数据类型为了与用户自定义类对象的语法对接,底层也实现了如int()、double()
这种构造函数.
int a(2);
cout << a << endl; //2 OK
decltype关键字
“declare type”的缩写。,在C++中,作为操作符,用于查询表达式的数据类型。decltype在C++11标准制定时引入,主要是为泛型编程而设计,以解决泛型编程中,由于有些类型由模板参数决定,而难以(甚至不可能)表示之的问题。
注意:decltype只进行表达式类型推导,不进行表达式求值
template<typename T>
void judge(T a) {
cout << "unknow type" << endl;
}
template<>
void judge(int a) {
cout << "int type" << endl;
}
template<>
void judge(double a) {
cout << "double type" << endl;
}
decltype(1 + 2) t1;
decltype(1.0 + 2) t2;
decltype(1 + 2.0) t3;
decltype('a') t4;
int main() {
judge(t1); //int type
judge(t2); //double type
judge(t3); //double type
judge(t4); //unknow type
return 0;
}
auto和decltype的对比
C++11新加了两个关键字:auto和decltype。用于在编译期推导出变量或表达式的类型,方便开发者简化代码。
auto
编译期推导变量类型。 例:auto x = 1;
推导规则
-
必须初始化。
-
在一行定义多个变量时,不能有二义性。
-
无法推导出模板参数。
-
不能定义数组
-
不能用于函数参数
-
不保留引用或const属性。即使用const类型或引用类型的对象来初始化auto对象,那auto也仅仅按照其基本类型进行推导。如果希望推导出引用或者const类型对象的话需要明确指出
const int ci = i, &cr = ci; auto b = ci; //b是一个int (ci的顶层const特性被忽略了) auto c = cr; //c是一个int (cr是ci的别名,ci本身是一个顶层const) auto d = &i; //d是一个int* (整数的地址就是指向整数的指针) //如果希望推断出的auto类型是一个顶层const,需明确指出 const auto f = ci; //ci的推断类型为int,f是const int //还可以将引用的类型设为auto auto &g = ci; //g是一个整型常量引用,绑定到ci auto &h = 42; //错误:不能为非常量引用绑定字面值 cosnt auto &j = 42; //正确:可以将常量引用绑定字面值
什么时候使用auto?
-
不影响代码可读性为前提。
-
作用域范围较小的变量尽量使用auto。
-
不关心类型的时候。
-
auto必须初始化,可以强制写代码时赋默认值。
decltype
-
与auto不同,decltype保留引用和const属性
cosnt int ci = 0, &cj = ci; decltype(ci) x = 0; //x的类型是const int decltype(cj) y = x; //y的类型是const int&,y绑定到变量x decltype(cj) z; //错误,z是一个引用,必须初始化
-
若exp是左值,decltype(exp)是exp类型的左值引用
int a = 0, b = 0; decltype(a + b) c = 0; // c是int,因为(a+b)返回一个右值 decltype(a += b) d = c;// d是int&,因为(a+=b)返回一个左值
返回值后置
在 C++11 中增加了返回类型后置(trailing-return-type,又称跟踪返回类型) 语法,将 decltype
和 auto
结合起来完成返回值类型的推导
class A {
public:
A() = delete;
A(int x) : x(x) {}
int x;
};
class B {
public:
B() = delete;
B(int x) : x(x) {}
int x;
};
class C {
public:
C() = default;
C(int x) : x(x) {}
int x;
};
//给decltype推导的表达式提供正确的运算规则
C operator+(const A &a, const B &b) {
return C(a.x + b.x);
}
C operator+(const B &b, const A &a) {
return C(a.x + b.x);
}
// 注意
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
ostream& operator<<(ostream &out, const C &c) {
out << c.x;
return out;
}
int main() {
A a(3);
B b(4);
cout << add(a, b) << endl; //7
cout << add(b, a) << endl; //7
return 0;
}