函数模板
#include <iostream> // 多个参数的函数木板 template<typename T1, typename T2> T2 max(T1 a, T2 b) { using namespace std; cout << "调用的自定义模板函数...... " << endl; return b < a ? a : b; } // 显式指定模板参数的类型 template<typename T> T max1(T a, T b) { using namespace std; cout << "显式指定模板参数...... " << endl; return b < a ? a : b; } int main() { using namespace std; double a = ::max(2, 3.2); cout << "max(2, 3.2) = " << a << endl; double b = ::max1<double>(2, 4); cout << "max1<double>(2, 4) = " << b << endl; return 0; }
函数模板有两种不同类型的参数:
模板参数:在尖括号中声明的
template<typename T>
调用参数:函数模板名称的括号中声明的
T max(T a, T b)
如果使用了多个模板类型参数的话,返回值有时候是个问题,对此有三种方法:
- 为返回值单独引入一个模板类型参数
- 让编译器找到返回类型
- 声明一个其他类型的共同类型
返回值的模板参数
函数模板可以自动检测模板参数类型,因此我们可以不同显式指定,当然显式指定也可以的。
比如:
#include <iostream> // 显式指定模板参数的类型 template<typename T> T max1(T a, T b) { using namespace std; cout << "显式指定模板参数...... " << endl; return b < a ? a : b; } int main() { using namespace std; double b = ::max1<double>(2, 4); cout << "max1<double>(2, 4) = " << b << endl; return 0; }
在进行 ::max1<double>(2, 4); 调用的时候,就是显式指定了模板参数。
现在来一个为返回值引入第三个模板参数:RT max(T1 a, T2 b);
#include <iostream> // 多个参数的函数木板 template<typename T1, typename T2, typename RT> RT max(T1 a, T2 b) { using namespace std; cout << "调用的自定义模板函数...... " << endl; return b < a ? a : b; } int main() { using namespace std; double a = ::max(2, 3.2); cout << "max(2, 3.2) = " << a << endl; return 0; }
然后编译的时候就报错了:
报错信息提示:参数 deduction(推理)失败了
这里引出了自动推理的规则:
如果模板参数和调用参数的类型没有什么关系,那么在调用的时候 参数模板的所有类型是不能完全确定的,你必须显式指定模板参数。
因此显式指定以下就可以了:
#include <iostream> // 多个参数的函数木板 template<typename T1, typename T2, typename RT> RT max(T1 a, T2 b) { using namespace std; cout << "调用的自定义模板函数...... " << endl; return b < a ? a : b; } int main() { using namespace std; // 模板参数和调用参数不能完全匹配的时候需要显式指定 double a = ::max<int, double, double>(2, 3.2); cout << "max(2, 3.2) = " << a << endl; return 0; }
编译通过,运行成功。
但是指定所有的模板类型又有点啰嗦,这里又来了一条规则:
你必须显式指定直到最后一个不能确定的所有模板类型。
比如上面例子中,RT 的类型是不能通过参数类型推理来确定的,所以导致 T1 和 T2 的类型都要显式指定。
因此这里来了一个骚操作,就是把不能指定的模板类型都放在模板参数列表的最前面,那么后面可以确定的模板参数就可以不用显式指定了。
#include <iostream> // 多个参数的函数木板 template<typename RT, typename T1, typename T2> RT max(T1 a, T2 b) { using namespace std; cout << "调用的自定义模板函数...... " << endl; return b < a ? a : b; } int main() { using namespace std; // 这个 double 专门为了 RT 而指定的, T1 和 T2 都可以通过调用参数推导出来 double a = ::max<double>(2, 3.2); cout << "max(2, 3.2) = " << a << endl; return 0; }
我只想说 C++ 是真的骚。
编译器推理返回类型
如果返回类型依赖于模板参数,那么最简单的方法就是让编译器去推导类型。
C++ 14 中已经可以不用指定返回类型了,但是你还是要用 auto 来声明返回类型。
如下面的例子:
#include <iostream> // 多个参数的函数木板 template<typename T1, typename T2> auto max(T1 a, T2 b) { using namespace std; cout << "调用的自定义模板函数...... " << endl; return b < a ? a : b; } int main() { using namespace std; // 这个 double 专门为了 RT 而指定的, T1 和 T2 都可以通过调用参数推导出来 auto a = ::max(2, 3.2); auto b = ::max(5, 1.2); cout << "max(2, 3.2) = " << a << endl; cout << "max(5, 1.2) = " << b << endl; return 0; }
大爷的,我又要去学下 auto 是怎么用的。
使用了 auto 就可以不用 trailing of return type(返回类型后置),但是实际的返回类型还是要通过函数体中的返回语句来推导。
当然了,根据函数体来反推返回值类型要是可行的。
decltype
decay
看到这里有点懵逼。
公共类型作为返回类型
C++ 11 中,标准库提供了一种对多个类型生成共同类型的方法。
例如:
#include <iostream> // 引入这个玩意儿生成共同类型 #include <type_traits> // 骚里骚气啊 template<typename T1, typename T2> std::common_type_t<T1, T2> max(T1 a, T2 b) { using namespace std; cout << "调用的自定义模板函数...... " << endl; return b < a ? a : b; } int main() { using namespace std; // 这个 double 专门为了 RT 而指定的, T1 和 T2 都可以通过调用参数推导出来 auto a = ::max(2, 3.2); auto b = ::max(5, 1.2); cout << "max(2, 3.2) = " << a << endl; cout << "max(5, 1.2) = " << b << endl; return 0; }
std::common_type 是一种 type trait,能够为返回类型生成一种结构。
typename std::common_type<T1, T2>::type // since C++ 11
而在 C++ 14 中
std::cmmon_type_t<T1, T2> // C++ 14 中的写法
服!气!