函数模板,function template
template <typename T, typename Y> T my_max(Y x, Y y) { return T(x) + T(y); }
template <class T> T my_min(T x, T y) { return x < y ? x : y; }
上面给出了两个例子,在模板的参数类型表示上,typename关键字和class关键字的效果完全相同
(这句话不完全正确,本文的进阶部分中的 “ 3.2 模板本身作为模板形参 ” 中,只能用 typename )
(但通常写的都是带类型的模板参数,此时这句话正确)
即,class关键字不仅能用来申明一个类,还可以用来作为参数类型的关键字声明
typename关键字是在class关键字后面引入的,是因为还有新的需要,参考:typename关键字声明嵌套依赖类型
[C++] 函数模板-目录
1.有类型函数模板的一般格式
1.1 格式一
template <class T_1 [, class T_2 [, ...[, class T_i]]]>
func_ret_type func_name(type_1 para_1, type_2 para_2, [, ...[, type_i para_i]]) {
// code
ret [ret_type_value];
}
1.2 格式二
template <typename T_1 [, typename T_2 [, ...[, typename T_i]]]>
func_ret_type func_name(type_1 para_1, type_2 para_2, [, ...[, type_i para_i]]) {
// code
ret [ret_type_value];
}
1.3 解释
1.3.1 从普通函数到“模板函数”
模板函数和普通函数的唯一区别就是在普通函数的正上方加上了一行“模板参数声明”:
template <class T_1 [, class T_2 [, ...[, class T_i]]]>
或者:
template <typename T_1 [, typename T_2 [, ...[, typename T_i]]]>
首先是关键字 template 加上一对尖括号 “<>” ,然后在 "<>" 中间使用 class 或者 typename关键字声明
接下来要在这个函数中使用的类型的名称,例如 :T_1, T_2, T_3, .... / Y_1, Y_2, Y_3, ...
之后,就可以在这个 “模板函数” 中自由地使用第一行导入的各种类型了,例如:
template <class T, class Y>
int a_weird_func(Y x, T y) {
Y y1;
T t1;
// Do something arcane
return 0;
}
1.3.2 Code
test_a_weird_func.cpp
#include <iostream>
using std :: cout;
using std :: endl;
template <class T, class Y>
int a_weird_func (Y x, T y) {
Y y1;
T t1;
// Do something arcane
cout << "Finished, return." << endl;
return 0;
}
int main (void) {
a_weird_func(1, 2.0);
return 0;
}
输出结果:
2. 有类型函数模板与重载
使用函数模板的时候会遇到一个问题:如果遇到一个函数的调用,有两个同名的函数可以调用,一个是函数模板定义的函数,一个是不带类型参数的普通函数,此时应该调用哪一个呢?
这就必须要掌握种函数重载情况下,模板函数和普通函数的调用的顺序
否则编写程序的时候,就无法正确地控制函数调用的逻辑
2.1 普通函数和模板函数的重载
2.1.1 func_template.cpp
#include <iostream>
#include <string>
using namespace std;
template <class T>
T my_min(T x, T y) {
cout << endl <<"Template min calling" << endl;
return x < y ? x : y;
}
template <typename T, typename Y>
T my_max(Y x, Y y) {
cout << endl << "Template max calling" << endl;
return T(x) + T(y);
}
/*
* For the two different template models:
*
* template <class T> VS template <typename T>
*
* Notes :
*
* The meaning of 'typename' and 'class' are same here
*
*/
double my_min(double x, double y) {
cout << endl << "Manipulated double min(double, double) calling" << endl;
return x < y ? x : y;
}
int my_max(int x, int y) {
cout << endl << "Manipulated int max(int, int) calling" << endl;
return x > y ? x : y;
}
int main() {
double r0 = my_min(1.0, 1.0);
// Handmade matches
// Called the handmade
// double (func) (double, double)
// get the result double(1.0)
// no convention is needed
// into double
cout << "my_min(1.0, 1.0) = " << r0 << endl << endl;
double r1 = my_min(1, 2);
// Handmade doesn't matche
// Called the templates
// template <class T>
// T (func) (T, T)
// So instantiate the template function into
// int (func) (int, int)
// got the result 1, then convert int(1) into double
// into double
cout << "my_min(1, 2) = " << r1 << endl << endl;
double r2 = my_max(1, 2);
// Handmade matches
// Called the handmade
// int (func) (int, int)
// get the result int(1)
// and then convert it
// into double
cout << "my_max(1, 2) = " << r2 << endl << endl;
double r3 = my_max(1.5, 2.5);
// Initially, the handmade int (func) (int, int)
// does not match
// Then check template, try to instantiate it
// into match the (1.5, 2.5), falied in the end
// coz template asks for 2 different type
// So try automatic convertion to fit the handmade
// first convert the int(1.5) -> 1 int(2.5) -> 2
// And then called the handmade
// int (func) (int, int)
// get the result 2
cout << "my_max(1.5, 2.5) = " << r3 << endl << endl;
int r4 = my_min(1.6, 2.6);
// Called the handmade
// double (func) (double, double)
// get the result 1.6
// Then convert it into the int type
// int(1.6) = 1
cout << "my_min(1.6, 2.6) = " << r4 << endl << endl;
return 0;
}
输出结果:
2.1.2 结果分析
具体的解释,我已经写在了代码的注释里面
总结规律:当普通函数与对应的能够发生重载的模板函数同时出现的时候,按照以下的规则进行处理
- 首先检查普通函数,如果普通函数能够使用,就使用普通函数,停止匹配,否则进入第二步
- 尝试实例化模板函数,如果能够实例化成功,就进行实例化,并使用,否则进入步骤3
- 回到第一步,尝试进行类型转换之后再次与普通函数进行匹配,如果类型转换能够成功,则结束,否则进入4
- 报错
注意:
函数匹配的时候,只看:
- 函数名
- 参数个数
- 参数类型(参数模板中声明的参数可以代表任意类型)
不看:
- 返回值类型
这是为什么呢?
因为函数的重载规定了,如果两个函数的函数名和参数列表(参数个数,类型)是相同的,那么重载无效
这也符合函数的定义,作用域和作用法则共同决定了输出,在C++中,作用域是通过参数列表确定的
C++的编译器不会去检查函数体的内容,所以作用法则简单粗暴的就用函数名来区分
所以函数名+参数列表已经唯一地确定的了一个函数,C++编译器在做函数匹配的时候,就检查这两个
一旦匹配成功,根据匹配的函数计算返回值,之后是否进行类型转换,是在函数调用完成后的赋值步骤进行判断的
如果数据类型不是C++自动支持的无损的转化类型,可能会发生精度的丢失,例如上面的例子中的r4
如果返回类型无法转换,就会报错:
func_can_not_convert.cpp
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
int my_max(double x, double y) {
cout << " int (func) (double, double) calling" << endl;
return x > y ? x : y;
}
string my_max(int x, int y) {
cout << " string (func) (int, int) calling" << endl;
int result = x > y ? x : y;
stringstream ss;
ss << result;
string str_result;
ss >> str_result;
return str_result;
}
int main() {
cout << " my_max(1, 2) = " + my_max(1, 2) << endl;
int result = my_max(1, 2);
return 0;
}
编译的时候,就报错了:
如果去掉不能进行数据类型转换的一行:
int result = my_max(1, 2);
就可以正常编译:
2.2 模板函数之间的相互重载
2.2.1 template_overload.cpp
#include <iostream>
#include <string>
using namespace std;
/*
* template <typename T>
* void print_arr(T start) {
* cout << "template <typename T> void (func) (T*) calling"
* << endl << endl;
* for (auto ele : start) {
* cout << ele << " ";
* }
* cout << endl << endl;
* }
*
* Syntax Error
*/
template <typename T>
void print_arr(T* start, int len) {
cout << "template <typename T> void (func) (T*, int) calling"
<< endl << endl;
for (int i = 0 ; i < len ; i++) {
cout << start[i] << " ";
}
cout << endl << endl;
}
template <typename T>
void print_arr(T* start, int begin, int end) {
cout << "template <typename T> void (func) (T*, int, int) calling"
<< endl << endl;
for (int i = begin ; i < end ; i++) {
cout << start[i] << " ";
}
cout << endl << endl;
}
int main(void) {
int int_arr[] = {1, 2, 3, 4, 5};
char ch_arr[] = {'a', 'b', 'c', 'd'};
string str_arr[] = {"hello", "world", "!"};
print_arr(int_arr, 5);
print_arr(int_arr, 0, 5);
print_arr(ch_arr, 4);
print_arr(ch_arr, 0, 5);
print_arr(str_arr, 3);
print_arr(str_arr, 0, 3);
return 0;
}
输出结果:
3. 进阶
前面的1、2部分介绍了类型参数作为模板形式参数的用法和调用顺序,这种函模板是最常见的
但是这是不够的,原因如下:
1. 看源码需要
例如:目前学习的有类型模板可以解释 opencv 中的 img.ptr<uchar>(num)
即从其函数声明中看到,template<typename _Tp> _Tp* ptr(int row, int col) uchar是对应的函数模板的参数
但是OpenCV库中同时也充斥着下面这种
explicit Mat_(const Point3_<typename DataType<_Tp>::channel_type>& pt, bool copyData=true);
此时我们又该怎么理解?2. 写出高质量程序必备
3.1 非类型参数作为模板形参
参考:auto关键字
3.2 模板本身作为模板形参