C++函数模板

类型推断

        函数模板在编译过程中,会进行类型推断,平时使用到隐式类型转换(自动类型转换),在类型推断时,几乎全部失效。经常用到的隐式类型转换包含以下几种:

  • 从低精度类型到高精度类型的转换,如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)
{
    //1.从低精度到高精度的类型转换
    int i0 = 10;
    double d0 = 6.98;
    max1(i0, d0); //candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'double')
    max2(i0, d0); //candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'double')
    max3(i0, d0); //candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'double')
    
    //2. 数组和指针
    char *str = "hello";
    char buf[] = "world";
    max1(str, buf); //OK max(char *, char *)
    max2(str, buf); //candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'char[6]')
    max3(str, buf); //candidate template ignored: deduced conflicting types for parameter 'T' ('char *' vs. 'char[6]')
  

    //3. 构造函数
    char *str0 = "hello";
    std::string str1("world");
    max1(str0, str1); //deduced conflicting types for parameter 'T' ('const char *' vs. 'std::string' (aka 'basic_string<char>'))
    max2(str0, str1); //deduced conflicting types for parameter 'T' ('const char *' vs. 'std::string' (aka 'basic_string<char>'))
    max3(str0, str1); //deduced conflicting types for parameter 'T' ('const char *' vs. 'std::string' (aka 'basic_string<char>'))

    //4. 继承关系
    A a0(1);
    B b0(32);
    A &a1 = b0;
    max1(a0, b0); //deduced conflicting types for parameter 'T' ('A' vs. 'B')
    max2(a0, b0); //deduced conflicting types for parameter 'T' ('A' vs. 'B')
    max3(a0, b0); //deduced conflicting types for parameter 'T' ('A' vs. 'B')
    
    //5. 非const到const的转换
    int nonc_i = 15;
    const int c_i =123;
    max1(nonc_i, c_i); //OK max1(int, int)
    max2(nonc_i, c_i); //deduced conflicting types for parameter 'T' ('int' vs. 'const int')
    max3(nonc_i, c_i); //OK max3(const int &, const int &)
    
    //6. 引用
    int raw_i = 100;
    int &ref_i = raw_i;
    int new_i = 0;
    max1(ref_i, new_i); //OK max1(int, int)
    max2(ref_i, new_i); //OK max3(int &, int &)
    max3(ref_i, new_i); //OK max3(const int &, const int &)
    
}

        通过上面的测试代码可以,大体可以得出以下几点结论:

  • 从低精度到高精度的类型转换,构造函数产生的类型转换,继承关系产生的类型转换,无论函数模板的形参是值、还是非const引用,还是const引用,都无法生效
  • 非const引用对类型的要求最为严格
  • 数组到指针的转换只能在函数模板形参为值时生效
  • 数组退化成指针,char[]->char *
  • 引用退化,int&->int
  • const属性退化,const int->int

        程序的二进制结果(注释掉无法通过编译的源码)如下:

        如果使用场景确实出现实参类型不同的情况,该如何处理呢?下面是三种不同的解决方案:

  • 强制类型转换
max1(static_cast<double>(3), 3.14);
  • 显示指出T的类型,告诉编译器不做类型推导
max1<double>(3, 3.14);
  • 使用多个模板参数,指明参数类型有可能不相同
template <typename T1, typename T2>
T1 max1(T1 x,  T2 y)

多模板参数

        多模板参数很好理解,但在某些情况下,如上文中的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.14
    auto temp2 = max1(3, 4.14); //temp2 = 4
    
	return 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);
    auto temp2 = max2(3, 4.14);
    
	return 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;
}

  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值