【C++编程】2. 模板实参推断
模板实参推断:对于函数模板,编译器利用调用中的函数实参来确定模板参数,从函数实参来确定模板参数的过程被称为模板实参推断。
类型转换与模板类型参数
与往常一样,顶层const无论在形参中还是在是实参中,都被会忽略。
• const转换:可以将一个非const对象的引用(或指针)传递给const的引用(或指针)形参。
• 数组或函数指针转换:一个数组实参可以转换为一个指向其首元素的指针。类似的,一个函数实参可以抓转换一个该函数类型的指针。
1 template <typename T> T fobj(T, T) //实参被拷贝 2 template <typename T> T fret(const T&, const T&) //引用 3 4 string s1("a value"); 5 const strng s2("another value"); 6 fobj(s1, s2); //调用fobj(string, string); const被忽略 7 fret(s1, s2); //调用fret(const string&, const string&); const被忽略 8 9 int a[10], b[42]; 10 fobj(a, b); //调用fobj(int*, int*); 11 fret(a, b); //错误
使用相同的模板参数类型的函数形参
一个模板类型形参可以用作多个函数形参的类型,由于只允许有限的几种类型转换,因此传递给这些形参的参数必须具有相同的类型。
1 template <typename T> 2 int compare(const T& v1, const T& v2) 3 { 4 if (v1 < v2) return -1; 5 if(v2 > v1) return 1; 6 return 0; 7 } 8 9 long lng; 10 compare(lng, 1024); //错误:不能实例化compare(long, int); 11 12 template <typename A, typename B> 13 int compare(const A& v1, const B& v2) //实参类型不一样,但必须兼容 14 { 15 if (v1 < v2) return -1; 16 if(v2 > v1) return 1; 17 return 0; 18 }
正常类型转化应用于普通函数实参
函数模板可以有用普通类型定义的参数,即,不涉及模板类型参数的类型,这种函数实参不进行特殊处理。
1 template <typename T> 2 ostream &print(ostream *os, const T &obj) 3 { 4 return os << obj; 5 } 6 7 print(cout, 42); //使用print(ostream &os, const int &obj); 8 ofstream f("output"); 9 print(f, 10); //使用print(ostream &os, const int &obj); 将f转换为ostream&
函数模板显示实参
在C++中,若函数模板返回类型需要用户指定,那么在定义函数模板时,模板参数的顺序是很重要的,如下代码:
template <typename T1, typename T2, typename T3> //模板一 T1 sum(T2 a, T3 b) { return a + b; }
在调用的时候就需要指定T1的类型,如:sum<float>(1,2);于是sum函数的返回类型为float。
但是有时候由于设计者的糟糕设计,会导致一些问题:
template <typename T1, typename T2, typename T3> //模板二 T3 sum(T1 a, T2 b) { return a + b; }
那么再进行上述调用,则会出现问题:sum<float>(1, 2);现在这个调用里指定T1类型为float,但是实际传进来的是1(int类型),会进行隐式类型转换,将1转换为float;T2的类型也可以根据sum(1,2)调用的第二个实参推断出来,这里是可能会是int。那么T3是什么类型呢?显然这里编译器无法推断T3的类型,需要在调用时指定才能推断:
sum<int, int, int>(1, 2),这样T3就推断出来是int。
在指定显示模板实参时指定的类型是和模板参数匹配的,顺序是一一对应的,如:
- 使用sum<int>(1, 2)对上面的第一个模板进行调用,那么T1对应int,T2和T3则通过推断得出。
- 使用sum<int>(1, 2)对上面的第二个模板进行调用,那么T1对应的类型是int,T2可以根据实际穿进去的参数进行推断,这里2为int那么T2类型就是int,那么编译器就无法知道T3的实际类型了。
例1:
1 template<typename T1, typename T2, typename T3> 2 T1 sum(T2 t2, T3 t3) 3 { 4 return t2 + t3; 5 } 6 7 template <typename T3, typename T2, typename T1> 8 T3 alternative_sum(T2 t2, T1 t1) 9 { 10 return t2 + t1; 11 } 12 13 int main() 14 { 15 int i = 1; 16 long lng = 10; 17 auto val1 = sum<long long>(i, lng); 18 19 auto val2 = alternative_sum<long long, int, long>(i, lng); 20 // auto val3 = alternative_sum<long long>(i, lng); 21 return 0; 22 }
尾置返回类型与类型转换
函数指针和实参推断
转发
某些函数需要将一个或多个实参连同类型不变地转发给其他函数。在次情况下,我们需要保持被转发实参的所有性质,包括实参类型是否是const的以及实参是左值还是右值。
1 //接受一个可调用对象和另外两个参数的模板 2 template <typename F, typename T1, typename T2> 3 void flips(F f, T1 t1, T2 t2) 4 { 5 f(t2, t1) 6 } 7 8 void f(int v1, int &v2) //注意v2是一个引用 9 { 10 cout << v1 << " " << ++t2 << endl; 11 } 12 13 f(42, i); //f改变了实参i 14 flip1(f, j, 42); //通过flip1调用不会改变j 15 // void flip1(void(*fcn)(int, int&), int ti, int t2);
定义能保持类型信息的函数参数
1 template <typename F, typename T1, typename T2> 2 void flips(F f, T1 &&t1, T2 &&t2) 3 { 4 f(t2, t1) 5 } 6 7 void f(int v1, int &v2) //注意v2是一个引用 8 { 9 cout << v1 << " " << ++t2 << endl; 10 } 11 12 flip1(f, j, 42);
1 template <typename F, typename T1, typename T2> 2 void flips(F f, T1 &&t1, T2 &&t2) 3 { 4 f(t2, t1) 5 } 6 7 void g(int &&v1, int &v2) 8 { 9 cout << v1 << " " << ++t2 << endl; 10 } 11 12 flip1(g, j, 42); //错误:不能从一个左值实例化int&&
在调用中使用std::forward保持类型信息
• forward定义在头文件utility中,与move不同,forward必须显式模板来调用。
• foeward返回该类型显式实参类型的右值引用,即,forward<T>的返回类型是T&&。
1 template <typename F, typename T1, typename T2> 2 void flips(F f, T1 &&t1, T2 &&t2) 3 { 4 f(t2, t1) 5 } 6 7 void g(int &&v1, int &v2) 8 { 9 cout << v1 << " " << ++t2 << endl; 10 } 11 12 flip1(f, j, 42); //错误:不能从一个左值实例化int&& 13 14 修改为: 15 16 template <typename F, typename T1, typename T2> 17 void flips(F f, T1 &&t1, T2 &&t2) 18 { 19 f(std::forward<T2> (t2), std::forward<T1> (t1)) 20 } 21 22 void g(int &&v1, int &v2) 23 { 24 cout << v1 << " " << ++t2 << endl; 25 } 26 27 flip1(f, j, 42); //i将以int&类型传递给g,42将以in&&类型传递给g
编写可变参数函数模板
1 #include <iostream> 2 #include <string> 3 4 template<typename T> 5 std::ostream& print(std::ostream &os, const T &t) 6 { 7 return os << t; 8 } 9 10 template<typename T, typename... Args> 11 std::ostream &print(std::ostream &os, const T &t, const Args&... rest) 12 { 13 os << t << ", "; 14 return print(os, rest...); 15 } 16 17 int main() 18 { 19 int i = 0; 20 std::string s = "how now brown cow"; 21 22 23 print(std::cout, i, s, 42); //0, how now brown cow, 42 24 return 0; 25 }