C++ day12 (三) 函数模板

终于接触模板了。之前看到模板,泛型,都是模模糊糊,只知其一不知其二,现在好好捋顺。而且泛型总给我一种很玄妙的感觉,哈哈哈

模板自然也是C++在C基础上加的

文章目录

  • 函数模板(使用泛型定义,常被放在头文件中)
    • 基础
      • 是啥
      • 为啥需要模板这种特性(以便把同一个算法用于不同类型的函数)
      • 怎么编写模板定义(关键字template, typename)
      • 示例1 交换int,double, char
    • 进阶
      • 重载模板的定义
        • 示例(机智的我还在函数模板里用了默认参数)
    • 模板的局限性: 对于复合类型很容易不能达到效果
      • 把模板定义显式具体化explicit specialization
        • 举个栗子 按次序使用原型
  • 具体化 specialization (都是函数定义,使用具体类型,不是通用描述)
    • 实例化 instantiation
      • 隐式实例化 implicit instantiation
      • 显式实例化 explicit instantiation(template关键字后面无一对空的尖括号)
    • 显式具体化 explicit specialization(template关键字后面有一对空的尖括号)
  • 重载解析 overloading resolution:寻找最匹配的函数定义
    • 示例 重载解析
    • 示例2 用尖括号或者显式实例化告诉编译器选哪一个
    • decltype (C++11, 没法解决函数模板的返回值不确定的情况)
      • decltype决定类型的四个步骤
      • typedef和decltype结合使用
    • 用auto和后置返回类型解决返回值类型不确定的痛点
      • 示例 果然规则多了容易出错(后置返回类型和显式实例化一起用,易出错)
  • 总结

函数模板(使用泛型定义,常被放在头文件中)

其实并不玄妙,反而是一个存在地极其合理的好工具。

C++发展初期,很多很多人都不看好模板,没想到函数模板以及模板类后来会这么强大有用。
但是也有一群程序员挑战模板技术的极限,阐述了各种可能。
正是熟悉模板的程序员们的反馈帮助C++98添加了标准模板库。

基础

是啥

函数模板使用泛型定义函数,而不是具体类型,所以函数模板是通用的函数描述,模板有时候也被称为通用编程

也就是说,模板并不创建任何函数,只是告诉编译器该怎么定义函数。这一点上,函数模板很像结构声明,结构声明也叫结构描述,结构模板的嘛,他也不创建具体的结构,只是告诉编译器创建结构的方案。

泛型可以用任意的具体类型替换,比如int, double。

把类型作为参数传给模板,以使得编译器生成该类型的函数(类似于文本替换)。由于类型用参数表示,所以模板特性也被称为参数化类型 parameterized types。

模板常被放在头文件里面,需要使用就包含这个头文件。可见模板确实和文本替换有关系,有点像宏。

为啥需要模板这种特性(以便把同一个算法用于不同类型的函数)

关键字:相同算法,不同类型

你想交换两个整数,于是你写了一个函数,两个参数类型是int &,返回类型是void

但是你又想交换两个double,于是你把刚才的代码复制下来,把int &都改成double &。

你又想交换两个char ,于是你又复制一遍刚才的代码,把int &改为char &。

·······

难道这就是你的编程生活?涂涂改改,缝缝补补的?是不是一点都感受不到编程之美了?你肯定也闻到了这个办法浓浓的蹩脚气息。你得想办法解决这个大痛点呀,毕竟光这么复制不仅显得愚笨,还费时间,替换修改还容易出错,更重要的是,增加了函数的数量,每个函数编译时都是要正经八百地申请内存居住的呀,如果我用到其中一个函数的机会其实并不多,那岂不是浪费内存吗?

所以C++ 针对这一痛点,祭出了模板特性,自动生成多个函数的定义,简单,省时,还可靠。

但是!不能缩短程序,即增加函数数量从而使用更多内存的痛点还是不能解决,因为使用模板编译出来的机器代码没有函数模板,只有具体的实际函数的代码

怎么编写模板定义(关键字template, typename)

之前学字符串的时候见过两个模板类,vector和array类。知道调用模板的时候要用尖括号把具体类型括起来,原来就是把类型传递给模板呢。

现在看看怎么编写模板的定义

template <typename T>//告诉编译器要建立一个模板,并把类型命名为T,也可以命名为AnyType等名称,满足标识符规则就行
//描述算法,即对T类型的参数做的操作
//可以看到,算法是完全一样的,不一样的只是放进来的参数类型而已
void myswap(T &a, T &b)
{
	T temp;
	
	temp = a;
	a = b;
	b = temp;
}

需要说明的是,以前(C++98添加关键字typename之前),创建模板用的关键字是class。到现在,也许仍然有大量的代码库使用了class关键字建立模板,看到的时候要知道是什么意思哦。在模板中,用typename和class是等价的,当然自己写模板就要用typename,毕竟是新标准,而且也很明确说明是类型名。

注意,函数模板的原型是:

template <typename T>
void myswap(T &a, T &b);

另外,并不是说函数模板的所有参数都得是泛型,也可以是具体类型,后面会有示例。

示例1 交换int,double, char

很简单

编译器会根据传入的参数类型自己生成一个正确类型的函数,真牛

#include 
template <typename T>
void myswap(T & a, T & b);

int main()
{
    using std::cout;

    int i = 10, j = 30;
    cout << "i = " << i << ", j = " << j << '\n';
    myswap(i, j);
    cout << "Using compiler-generated int swapper:\n";
    cout << "Now i = " << i << ", j = " << j << "\n\n";

    double x = 10.2, y = 30.1;
    cout << "x = " << x << ", y = " << y << '\n';
    myswap(x, y);
    cout << "Using compiler-generated double swapper:\n";
    cout << "Now x = " << x << ", y = " << y << "\n\n";

    char m = 'x', n = 'y';
    cout << "m = " << m << ", n = " << n << '\n';
    myswap(m, n);
    cout << "Using compiler-generated char swapper:\n";
    cout << "Now m = " << m << ", n = " << n << '\n';

    return 0;
}

template <typename T>
void myswap(T &a, T & b)
{
    T temp;

    temp = a;
    a = b;
    b = temp;
}
i = 10, j = 30
Using compiler-generated int swapper:
Now i = 30, j = 10

x = 10.2, y = 30.1
Using compiler-generated double swapper:
Now x = 30.1, y = 10.2

m = x, n = y
Using compiler-generated char swapper:
Now m = y, n = x

进阶

重载模板的定义

刚才说了,模板的关键是:同一算法,不同类型。

那要是不同类型做同一件事情的时候,算法不完全一样呢、
比如上面的示例,我要是传入两个数组,或者两个结构,用三变量交换法不能正常交换

那就像重载普通函数那样,重载模板,即两个同名模板的特征标(参数列表)不一样

示例(机智的我还在函数模板里用了默认参数)

并且我还把显示数组元素的函数也定义为了一个模板,学以致用了

#include 
template <typename T>
void myswap(T & a, T & b);
template <typename T>
void myswap(T a[], T b[], int n = 5);//两个数组;模板的参数不一定都是泛型
template <typename T>
void showArray(T ar[], int len);
const int arSize = 5;

int main()
{
    using std::cout;

    int i = 10, j = 30;
    cout << "i = " << i << ", j = " << j << '\n';
    myswap(i, j);
    cout << "Using compiler-generated int swapper:\n";
    cout << "Now i = " << i << ", j = " << j << "\n\n";

    double x = 10.2, y = 30.1;
    cout << "x = " << x << ", y = " << y << '\n';
    myswap(x, y);
    cout << "Using compiler-generated double swapper:\n";
    cout << "Now x = " << x << ", y = " << y << "\n\n";

    char m = 'x', n = 'y';
    cout << "m = " << m << ", n = " << n << '\n';
    myswap(m, n);
    cout << "Using compiler-generated char swapper:\n";
    cout << "Now m = " << m << ", n = " << n << "\n\n";

    int a[arSize] = {1, 2, 3, 4, 5};
    int b[arSize] = {10, 20, 30, 40, 50};
    cout << "Original array:\n";
    cout << "a: ";
    showArray(a, arSize);
    cout << "b: ";
    showArray(b, arSize);
    myswap(a, b, 3);
    cout << "New array:\n";
    cout << "a: ";
    showArray(a, arSize);
    cout << "b: ";
    showArray(b, arSize);

    cout << '\n';
    cout << "Original array:\n";
    cout << "a: ";
    showArray(a, arSize);
    cout << "b: ";
    showArray(b, arSize);
    myswap(a, b);
    cout << "New array:\n";
    cout << "a: ";
    showArray(a, arSize);
    cout << "b: ";
    showArray(b, arSize);

    return 0;
}

template <typename T>
void myswap(T &a, T & b)
{
	//这个代码是可以交换两个结构的哦,因为C++允许把一个结构赋值给另一个,但是不允许数组之间赋值
    T temp;

    temp = a;
    a = b;
    b = temp;
}

template <typename T>
void myswap(T a[], T b[], int n)
{
    //注意a,b都是指针!
    //虽然下面用数组表示法,但是可以修改参数
    T temp;

    int i;
    for (i = 0; i < n; ++i)
    {
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }

}

template <typename T>
void showArray(T ar[], int len)
{
    int i;

    for ( i = 0; i < len; ++i)
        std::cout << ar[i] << ' ';
    std::cout << std::endl;
}
i = 10, j = 30
Using compiler-generated int swapper:
Now i = 30, j = 10

x = 10.2, y = 30.1
Using compiler-generated double swapper:
Now x = 30.1, y = 10.2

m = x, n = y
Using compiler-generated char swapper:
Now m = y, n = x

Original array:
a: 1 2 3 4 5
b: 10 20 30 40 50
New array:
a: 10 20 30 4 5
b: 1 2 3 40 50

Original array:
a: 10 20 30 4 5
b: 1 2 3 40 50
New array:
a: 1 2 3 40 50
b: 10 20 30 4 5

模板的局限性: 对于复合类型很容易不能达到效果

局限性不是缺点,只是他做不到的事情。

就像上面这个示例展示的,我想交换两个数组的元素,之前写的第一个函数就不行了。

再比如,我在函数模板里写了a = b;,那么如果传入的是数组类型,那么赋值会报错;
如果我在函数模板里写了if (a > b),如果传入的是数组或者结构,都不行,因为数组名实际是地址,这其实是在比较地址大小,但这可能并不是我们想要的结果。
如果在函数模板里定义了a * b,那么传入指针,结构, 数组都不行。

总之,函数模板提供的通用化并不是一夫当关万夫莫开,一般数组,结构,指针等复合类型很容易就把关打开了。

怎么办呢?

  • 1.重载运算符。比如我重新定义假发运算符+,使得可以把两个特定的结构相加,比如表示位置坐标的结构。
  • 2.为特定类型提供具体化的模板定义。

把模板定义显式具体化explicit specialization

不知道为啥,这一点看的我特别难受,感觉很看不下去,不过看懂了觉得也不难,可能是学习过度了····

显示具体化就是说,对一个模板,他如果不适用于某个类型,那我就用这个类型去具体化这一个模板,即专门写一个函数用于这个类型,但是这个函数仍然是这个模板的一部分。

一个函数名可以有普通函数,显式具体化的函数模板,普通函数模板以及这三者的重载版本
执行程序时,编译器按照顺序选择对应原型的函数来调用:如果找得到适合的普通函数就用普通函数,否则找显式具体化的函数模板,最后找普通函数模板

举个栗子 按次序使用原型

#include 
struct job
{
    char name[20];
    float salary;
    unsigned int floor;
};
//普通函数模板
template <typename T>
void myswap(T & a, T & b);
//显式具体化函数模板
//以template <>打头,中的job可以省略,因为参数列表也携带了类型信息
template <> void myswap<job>(job &, job &);
//常规函数
void myswap(job &, job &);
const int arSize = 5;
void showJob(job &a);

int main()
{
    using std::cout;

    int i = 10, j = 30;
    cout << "i = " << i << ", j = " << j << '\n';
    myswap(i, j);
    cout << "Using compiler-generated int swapper:\n";
    cout << "Now i = " << i << ", j = " << j << "\n\n";

    double x = 10.2, y = 30.1;
    cout << "x = " << x << ", y = " << y << '\n';
    myswap(x, y);
    cout << "Using compiler-generated double swapper:\n";
    cout << "Now x = " << x << ", y = " << y << "\n\n";

    char m = 'x', n = 'y';
    cout << "m = " << m << ", n = " << n << '\n';
    myswap(m, n);
    cout << "Using compiler-generated char swapper:\n";
    cout << "Now m = " << m << ", n = " << n << "\n\n";

    job sue = {"Susan Yaffee", 4562.85, 7};
    job sia = {"Sia Taylor", 5269.8, 9};
    cout << "Before job swapping:\n";
    showJob(sue);
    showJob(sia);
    myswap(sue, sia);
    cout << "After job swapping:\n";
    showJob(sue);
    showJob(sia);

    return 0;
}

template <typename T>
void myswap(T &a, T & b)
{
    T temp;
    std::cout << "Using template  void myswap(T &a, T & b) \n";

    temp = a;
    a = b;
    b = temp;
}

template <> void myswap(job & a, job &b)
{
	//显式具体化模板和常规函数的代码是一毛一样的哈
    float t1;
    unsigned int t2;
    std::cout << "Using template <> void myswap(job & a, job &b) \n";

    t1 = a.salary;
    a.salary = b.salary;
    b.salary = t1;

    t2 = a.floor;
    a.floor = b.floor;
    b.floor = t2;
}

void myswap(job &a, job & b)
{
    float t1;
    unsigned int t2;
    std::cout << "Using void myswap(job &a, job & b) \n";

    t1 = a.salary;
    a.salary = b.salary;
    b.salary = t1;

    t2 = a.floor;
    a.floor = b.floor;
    b.floor = t2;
}

void showJob(job &a)
{
    std::cout << a.name << ": $" << a.salary << " on floor " << a.floor << '\n';
}

可以看到,有常规函数时交换job类型使用的就是常规函数,而上面交换Int,double,char由于没有常规函数和显式具体化模板都使用了常规函数模板。

i = 10, j = 30
Using template <typename T> void myswap(T &a, T & b)
Using compiler-generated int swapper:
Now i = 30, j = 10

x = 10.2, y = 30.1
Using template <typename T> void myswap(T &a, T & b)
Using compiler-generated double swapper:
Now x = 30.1, y = 10.2

m = x, n = y
Using template <typename T> void myswap(T &a, T & b)
Using compiler-generated char swapper:
Now m = y, n = x

Before job swapping:
Susan Yaffee: $4562.85 on floor 7
Sia Taylor: $5269.8 on floor 9
Using void myswap(job &a, job & b)
After job swapping:
Susan Yaffee: $5269.8 on floor 9
Sia Taylor: $4562.85 on floor 7

把常规函数的定义和原型删掉,则程序选择了显式具体化模板,可见确实是上面所说的顺序

Before job swapping:
Susan Yaffee: $4562.85 on floor 7
Sia Taylor: $5269.8 on floor 9
Using template <> void myswap(job & a, job &b)
After job swapping:
Susan Yaffee: $5269.8 on floor 9
Sia Taylor: $4562.85 on floor 7

具体化 specialization (都是函数定义,使用具体类型,不是通用描述)

这里介绍几个概念或者术语。为了扎实的基础,应该把这些概念弄明白,才能进一步了解模板。

别晕了

隐式实例化,显式实例化,隐式具体化统称为具体化。
他们都是函数定义,使用了具体的类型,他们不是通用描述。

实例化 instantiation

说过了,函数模板不是函数定义,而是指导编译器生成函数定义的方案。那么编译器生成的函数定义就是函数模板的一个实例

注意实例化都是放在函数内部的,毕竟是生成实例嘛,当然在函数内部。而隐式具体化是放在所有函数外部的,和原型放在一起。

隐式实例化 implicit instantiation

隐式实例化就是编译器根据参数的类型自动生成模板的实例,比如前面的示例中myswap(i, j),由于i,j是int类型,就直接实例 化了一个参数为int的函数定义。

最初只允许隐式实例化

显式实例化 explicit instantiation(template关键字后面无一对空的尖括号)

  1. 直接命令编译器创建特定实例就是显示实例化,语法:
template void myswap<int>(int, int);

还是要用关键字template和尖括号,并必须在尖括号写上要实例化的类型。

  1. 还可以在程序中用函数创建显式实例化
    例子:
#include 
template <typename T>
T add(T a, T b);

int main()
{
    int m = 7;
    double x = 3.4;

    //x是double,m是int,和模板不匹配,但是函数名后面尖括号的double
    //把函数强制为double实例化,并把参数m强制转换为double类型
    std::cout << add<double>(x, m) << '\n';//显式实例化

    return 0;
}

template <typename T>
T add(T a, T b)
{
    return a + b;
}
10.4

但是这么做一定是可以类型转换成功才行,像下面这个例子,int无法转换为double &,就不行了

#include 
template <typename T>
void myswap(T &a, T & b);

int main()
{
    int m = 7;
    double x = 3.4;

    //myswap(x, m);//报错,因为double&不能指向int,转换不了,所以这个例子不行
    std::cout << x << '\n';//显式实例化

    return 0;
}

template <typename T>
void myswap(T &a, T & b)
{
    T temp;

    temp = a;
    a = b;
    b = temp;
}

显式具体化 explicit specialization(template关键字后面有一对空的尖括号)

上面已经说过了,显式具体化用于在模板的代码不适合某种类型时具体化,写一个适合这个类型的函数,但仍用模板关键字。

显式具体化的声明:(template <>打头)

template <> void myswap<int>(int &, int &);
template <> void myswap(int &, int &);//可以省略第二个尖括号

注意显式具体化在template关键字后面有一对空的尖括号。

显式具体化的函数原型都是有自己的函数定义的,编译器不会用myswap模板生成函数定义。

C++ day12 (三) 函数模板_第1张图片

重载解析 overloading resolution:寻找最匹配的函数定义

重载解析是指有函数重载,或者函数模板,或者函数模板重载的时候,编译器决定到底给每个函数调用使用哪一个函数定义。

这是一个很复杂的事情。大致来说:

  • 1.找到所有候选函数。即与被调函数同名的所有函数以及函数模板。
  • 创建可行函数列表。即参数数目正确,且类型转换可以实现。
  • 找到最佳可行函数。根据这个顺序
    C++ day12 (三) 函数模板_第2张图片
    C++ day12 (三) 函数模板_第3张图片
    在这里插入图片描述
    看来非模板函数的优先级是最高的,其次是具体化的函数

示例 重载解析

两个函数模板,重载,看编译器选择最匹配的一个

#include 
//重载模板
template <typename T>
void showArray(T arr[], int n);
template <typename T>
void showArray(T * arr[], int n);//指针数组,每个元素都是指向T的指针

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    showArray(a, 5);//T被替换为int

    double *b[4];//指针数组
    double c[4] = {2.1, 5.1, 8.1, 11.1};
    for (int i = 0; i < 4; ++i)
        b[i] = &c[i];
    showArray(c, 4);
    showArray(b, 4);

    return 0;
}

template <typename T>
void showArray(T arr[], int n)
{
    std::cout << "template A" << '\n';
    for (int i = 0; i < n; ++i)
        std::cout << arr[i] << ' ';//arr是指针,这里用数组表示法
    std::cout << '\n';
}

template <typename T>
void showArray(T * arr[], int n)
{
    std::cout << "template B" << '\n';
    for (int i = 0; i < n; ++i)
        std::cout << *arr[i] << ' ';//arr[i]是指针
    std::cout << '\n';
}
template A
1 2 3 4 5
template A
2.1 5.1 8.1 11.1
template B
2.1 5.1 8.1 11.1

如果没有模板B,则第三次调用回合模板A匹配,T被double *代替,但是打印的就是地址了

template A
1 2 3 4 5
template A
2.1 5.1 8.1 11.1
template A
0x6dfeb8 0x6dfec0 0x6dfec8 0x6dfed0

示例2 用尖括号或者显式实例化告诉编译器选哪一个

#include 
template <typename T>
T lesser(T a, T b);

int lesser(int a, int b);//非模板函数
int main()
{
    int m = 20, n = -40;
    double x = 34.5, y = -23.4;

    std::cout << lesser(m, n) << '\n';//非模板函数
    std::cout << lesser(x, y) << '\n';//模板函数,隐式实例化
    std::cout << lesser<>(m, n) << '\n';//用尖括号让编译器选择模板函数,并隐式实例化
    std::cout << lesser<int>(x, y) << '\n';//让编译器使用函数模板,并且在函数中显式实例化化为int类型

    return 0;
}

template <typename T>
T lesser(T a, T b)
{
    return (a < b) ? a : b;
}

int lesser(int a, int b)
{
    //这是一个错误的函数。为了实验而已
    a = (a < 0) ? -a : a;
    b = (b < 0) ? -b : b;

    return (a < b) ? a : b;
}
20
-23.4
-40
-23

decltype (C++11, 没法解决函数模板的返回值不确定的情况)

这个关键字在C++11增加,增加他肯定是为了解决什么痛点咯,痛点就是,函数模板中有的数据的类型无法在编写模板时就确定,比如

template <typename T1, typename T2>
void ft(T1 a, T2 b)
{	
	?type? x = a + b;// 不知道给x声明什么类型!!!
	...
}

如果a是double,b是int,则x是double
a是short,b是int,则x是int
总之写模板的时候是不能确定的

C++98没有解决这个问题,所以C++11新增了decltype关键字,但是这个关键字也没有完全地解决这个问题:它没法解决函数模板的返回值不确定的情况

给decltype的参数是一个表达式

template <typename T1, typename T2>
void ft(T1 a, T2 b)
{	
	decltype(a + b) x = a + b;// x的类型是表达式a+b的类型
	...
}

decltype决定类型的四个步骤

下面详细说一下这个关键字决定类型的步骤:
必须按照顺序来,如果已经满足了,就不会再往后查询,这4步并不是平等的。

  • 1.如果表达式是一个没有用括号括起来的标识符,则类型就是这个标识符变量的类型
double x = 2.3;
double & y = x;
const double *P = x;

decltype(x) w;//w是double类型
decltype(y) u;//u是double &类型
decltype(p) z;//z是const double *
  • 2.如果表达式是一个函数调用,则和返回值类型一样,但是编译器不会执行这个函数调用,只是去看看原型就好了
long ft(int);
decltype(ft(3)) x;//x是long类型

如果这个函数也有一堆重载的话,编译器大概也是像刚才说的那样去选择一个最佳匹配的函数定义,然后获得返回值类型

  • 3.如果表达式是一个左值,比如用括号括起来的标识符,则类型是指向这个左值的引用。
double x = 2.3;
decltype(x) y;//y是double类型,因为这是第一种情况
decltype((x)) z;//z是double &
  • 4.如果前三个都不满足,那么类型就和表达式的类型一样。
int x = 2;
int & a = x;
int & b = x;
decltype(x + 2) w;//int
decltype(x + 2.1) u;//double
decltype(100L) z;//long
decltype(a + b) p;//p是int!!不是int &,虽然a, b是int &,但是加起来的值仍然是int哦

typedef和decltype结合使用

template <typename T1, typename T2>
void ft(T1 a, T2 b)
{	
	typedef decltype(a + b) T3;
	T3 x = a + b;// x的类型是表达式a+b的类型
	T3 c[4];
	T3 &d = x;
	...
}

用auto和后置返回类型解决返回值类型不确定的痛点

decltype解决了函数模板内部的变量类型不确定的痛点,但是返回值类型不确定就没招。

于是C++11再次发力,拿出了后置返回类型,一种新的声明函数的方式。要利用auto关键字作为占位符。并在函数原型的参数后方写后置返回类型(trailing return type)。

这是C++11给auto新增的使用方法,如:

auto ft(int x, double y) -> float;
//还可以和decltype合作
auto ft1(int x, float y) ->decltype(x + y);//由于x,y写在参数列表后面,所以在作用域里,可以用

示例 果然规则多了容易出错(后置返回类型和显式实例化一起用,易出错)

素来听闻坊间传言,说是C++东西太多太灵活容易出错,之前不以为然,今日一见,果真名不虚传。我把这个后置返回类型和显式实例化一起用,结果就错了

但我出错不能怪C++,只怪自己技艺肤浅,ft(x, y)应该传入两个类型才对,因为模板里用的是两个类型

#include 
template <typename T1, typename T2>
auto ft(T1 x, T2 y) ->decltype(x + y);

int main()
{
    int m = 20, n = -40;
    double x = 34.5, y = -23.4;

    std::cout << ft(m, n) << '\n';//模板函数,隐式实例化
    std::cout << ft(x, y) << '\n';//模板函数,隐式实例化
    std::cout << ft<>(m, n) << '\n';//让编译器选择模板函数,隐式实例化
    std::cout << ft<int>(x, y) << '\n';//让编译器使用函数模板,并且在函数中显式实例化化为int类型

    return 0;
}

template <typename T1, typename T2>
auto ft(T1 x, T2 y) ->decltype(x + y)
{
	return x + y;
}

前三个输出都是对的,但是第四个输出了如此奇怪的结果,我通过调试发现,原来尖括号的int只把T1替换了!!T2仍然是隐式实例化为double ,所以得到了34-23.4=10.6

-20
11.1
-20
10.6

把最后后一个调用改为指定两个类型的显式实例化,就对了

std::cout << ft<int, int>(x, y) << '\n';
-20
11.1
-20
11

总结

一直对模板感到很好奇,没想到在学习类之前先学了一波模板,果然是接触到了新鲜气息,难倒不是很难,就是知识点比较多,需要多练习才能掌握好,感觉到了用处,但是我的好奇心还有很多没被满足,想看更多模板真正散发魅力的高光时刻。

你可能感兴趣的:(C++)