类型推断
模板在编译过程中,会进行类型推断,平时使用到隐式类型转换(自动类型转换),在类型推断时,几乎全部失效。经常用到的隐式类型转换包含以下几种:
- 从低精度类型到高精度类型的转换,如char->int,int->double
- 从数组到指针,如char []->char *
- 通过构造函数进行类型转换,例如char *->std::string,std::string str = "hello"
- 通过继承关系,子类对象引用自动转成基类对象引用,如B派生自A,对于函数A &max5(A &x, A &y), 定义对象 A a和对象B b,max(a, b)这样的调用是没有问题的
下面是一段测试代码:
#include <cstdio>
#include <string>template <typename T>
T max1(T x, T y)
{printf("x : %s, y : %s\n", typeid(x).name(), typeid(y).name());return x > y ? x : y;
}template <typename T>
T &max2(T &x, T &y)
{printf("x : %s, y : %s\n", typeid(x).name(), typeid(y).name());return x > y ? x : y;
}template <typename T>
const T &max3(const T &x, const T &y)
{printf("x : %s, y : %s\n", typeid(x).name(), typeid(y).name());return x > y ? x : y;
}class A
{
public:A(int i) : m_data(i) {}bool operator> (const A &rhs) const {printf("A\n");return m_data > rhs.m_data;}protected:int m_data;
};class B : public A
{
public:B(int i) : A(i) {}bool operator> (const B &rhs) const {printf("B\n");return m_data > rhs.m_data;}};int main(int argc, char **argv)
{char c0 = 'a';int i0 = 10;int *i0p = &i0;const int i1 = 14;double d0 = 3.14;char *s0 = "100";char s1[8] = "hello";const char *s2 = "world";std::string str0 = "200";A a0(10);B b0(12);A &a1 = a1;A &a2 = b0;max1(c0, i0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char' vs. 'int')max1(i0, d0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'double')max1(i0, i1); //int max1(int, int) 无法确认是否有const修饰符max1(s0, s1); //char *max1(char *, char *)max1(s0, str0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'std::string' (aka 'basic_string<char>'))max1(s0, s2); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'const char *')max1(a0, b0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('A' vs. 'B')max1(a1, a2); //A max1(A, A)max1(a0, a2); //A max1(A, A)max1(a0, b0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('A' vs. 'B')max2(c0, i0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char' vs. 'int')max2(i0, d0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'double')max2(i0, i1); //Candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'const int')max2(i0, i1); //Candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'const int')max2(s0, s1); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'char[8]')max2(s0, str0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'std::string' (aka 'basic_string<char>'))max2(s0, s2); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'const char *')max2(a0, b0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('A' vs. 'B')max2(a1, a2); //A max2(A, A)max2(a0, a2); //A max2(A, A)max2(a0, b0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('A' vs. 'B')max3(c0, i0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char' vs. 'int')max3(i0, d0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'double')max3(i0, i1); //int max3(int, int) 无法确认是否有const修饰符max3(s0, s1); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'char[8]')max3(s0, str0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'std::string' (aka 'basic_string<char>'))max3(s0, s2); //Candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'const char *')max3(a0, b0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('A' vs. 'B')max3(a1, a2); //A max3(A, A)max3(a0, a2); //A max3(A, A)max3(a0, b0); //Candidate template ignored: deduced conflicting types for parameter 'T' ('A' vs. 'B')}
通过上面的代码可以看出,模板使用非const引用传参是对变量类型要求最严格的,不允许任何隐式类型转换;模板使用const引用传参允许const类型和非const类型转换;模板使用值传参支持数组到同类型指针的转换,也支持const类型和非const类型的转换。关于const类型和非const类型转换,从代码看不出到底是const类型转换成了非const类型,还是非const类型转换成了const类型,但可以从最终的二进制文件看出max1是把const int转换成了int,max3是吧int转换成了const int,如下:
如果确实要使用到不同的类型,有3中处理方案:
- 强制类型转换
max1(static_cast<double>(3), 3.14);
- 显示指出T的类型,告诉编译器不做类型推导
max1<double>(3, 3.14);
- 使用多个模板参数,指明参数类型有可能不相同
多模板参数
多模板参数很好理解,但在某些情况下,如上文中的max函数,面临一个问题——如何选择返回值类型?为什么这个问题很重要,先来看一个例子,如下:
#include <cstdio>template <typename T1, typename T2>
T1 max1(T1 x, T2 y)
{return x > y ? x : y;
}int main(int argc, char **argv)
{auto temp1 = max1(4.14, 3); //temp1 = 4.14auto temp2 = max1(3, 4.14); //temp2 = 4return 0;
}
看到这样的结果,是不是感到很困惑?为什么会有这样的结果?从max1的定义可以看出,max1返回值的类型是第一个参数的类型。尽管两次调用的参数都是4.14和3,但第一次调用,返回值类型T1和4.4相同,为double,所以返回值为4.14;第二次调用返回值类型T1和3相同,为int,尽管计算结果和第一次相同为4.14,但返回时被转换int类型,变为4.
基于上面的问题,必须认真对待返回值的类型。关于上述问题,有三种解决方案:
- 专门为返回值声明一个模板类型
#include <cstdio>template <typename T1, typename T2, typename RT>
RT max2(T1 x, T2 y) {return x > y ? x : y;
}int main(int argc, char **argv)
{auto temp1 = max2(4.14, 3); //temp1 = 4.14auto temp2 = max2(3, 4.14); //temp2 = 4return 0;
}
上面的源码就可以运行了吗?(⊙o⊙)…编译失败了
Candidate template ignored: couldn't infer template argument 'RT'
Candidate template ignored: couldn't infer template argument 'RT'
原因是编译器仅对函数参数的类型进行推导,RT不是任何参数的类型,所以不会被推导。必须使用下面的方法调用:
auto temp1 = max2<double, int, double>(4.14, 3);auto temp2 = max2<int, double, double>(3, 4.14);
是不是很繁琐?一个不小心,又会翻船😄
- 返回值类型声明为auto,让编译器推导返回类型
#include <cstdio>template <typename T1, typename T2>
auto max3(T1 x, T2 y) {return x > y ? x : y;
}int main(int argc, char **argv)
{auto temp1 = max3(4.14, 3);auto temp2 = max3(3, 4.14);return 0;
}
上述测试代码仅对c++14,及以后版本生效。对于c++11,上述源码会报错:
error: 'auto' return without trailing return type; deduced return types are a C++14 extension
关于上述错误,可以使用decltype关键字对返回类型进行显示推导:
template <typename T1, typename T2>
auto max4(T1 x, T2 y) -> decltype(true ? x : y) {return x > y ? x : y;
}
注意,decltype操作符仅仅推导传入语句运算结果的的类型,不关心最终结果,因此,还可以如下实现:
template <typename T1, typename T2>
auto max4(T1 x, T2 y) -> decltype(x - y) {return x > y ? x : y;
}
再换种实现,看下是不是很想lambda表达式?
template <typename T1, typename T2>
auto max4(T1 x, T2 y) -> double {return x > y ? x : y;
}
- 将返回值设置为公共类型
template <typename T1, typename T2>
std::common_type_t<T1 , T2> max5(T1 x, T2 y) {return x > y ? x : y;
}
注意,此时声明中不能出现任何引用的痕迹,std::common_type_t<T1 &, T2 &> max5(T1 &x, T2 &y) ,或std::common_type_t<T1 , T2> max5(T1 &x, T2 &y)都是错误的方式。
Candidate function [with T1 = double, T2 = int] not viable: expects an lvalue for 1st argument
对于c++11,common_type_t的使用方式和此处有点不一样,下面是c++11的实现:
template <typename T1, typename T2>
typename std::common_type<T1 , T2>::type max6(T1 x, T2 y) {return x > y ? x : y;
}
默认模板参数
标题中的概念很好理解,就是给模板参数指定一个默认值。对于模板参数的默认值是直接指定,还是通过计算推导得出,编译器是不关心的,因此下面几种实现都是可以的:
#include <cstdio>
#include <type_traits>template <typename T1, typename T2, typename RT=double>
RT max1(T1 x, T2 y) {return x > y ? x : y;
}template <typename T1, typename T2, typename RT=std::decay_t<decltype(true ? T1() : T2())>>
RT max2(T1 x, T2 y) {return x > y ? x : y;
}template <typename T1, typename T2, typename RT=std::common_type_t<T1, T2>>
RT max3(T1 x, T2 y) {return x > y ? x : y;
}int main(int argc, char **argv)
{auto v1 = max1(7.98, 10);auto v2 = max2(7.98, 10);auto v3 = max3(7.98, 10);return 0;
}
模板函数重载
模板函数重载比普通函数重载更为复杂,模板参数的个数不同也可以算作重载,即使其中的某些模板参数不使用。但是,不要试图通过不同的模板参数名称去实现模板函数的重载,这样只会有两种可能:重复的函数定义;模板实例不知道调用哪个实现。
一个非模板函数可以和一个与其同名的函数模板共存,并且这个同名的函数模板可以被实例化为与非模板函数具有相同类型的调用参数。在所有其它因素都相同的情况 下,模板解析过程将优先选择非模板函数,而不是从模板实例化出来的函数。下面是测试代码:
#include <cstdio>
#include <cstring>template <typename T>
T max1(T x, T y) {return x > y ? x : y;
}// Redefinition of 'max1'
/*template <typename U>
U max1(U x, U y) {return x > y ? x : y;
}*/template <typename T, typename RT>
T max1(T x, T y) {return x > y ? x : y;
}template <typename T1, typename T2, typename RT=decltype(true ? T1() : T2())>
RT max1(T1 x, T2 y) {return x > y ? x : y;
}//如果存在下面的定义,m1将会不知道调用RT max1(T2 x, T1 y),还是RT max1(T1 x, T2 y)
/*template <typename T1, typename T2, typename RT=decltype(true ? T1() : T2())>
RT max1(T2 x, T1 y) {return x > y ? x : y;
}*///template<>,打开此处注释,m6会调用该函数
const char *max1(const char *s1, const char *s2) {return strcmp(s1, s2) > 0 ? s1 : s2;
}int main(int argc, char **argv)
{auto m1 = max1(100, 4.8); //RT max1(T1 x, T2 y)auto m2 = max1("abc", "efd"); //const char *max1(const char *s1, const char *s2)//auto m3 = max1("abc", 10000); //Candidate template ignored: substitution failure [with T1 = const char *, T2 = int]: incompatible operand types ('const char *' and 'int')auto m4 = max1(10, 245); //T max1(T x, T y)auto m5 = max1<int, int>(10, 245); //template <typename T, typename RT> T max1(T x, T y)auto m6 = max1<>("abc", "efd"); //T max1(T x, T y),强制使用模板函数return 0;
}