C++ 函数重载,函数模板和函数模板重载,选择哪一个?

简介:

C++ 函数重载,函数模板和函数模板重载,选择哪一个?

重载解析#
在C++中,对于函数重载、函数模板和函数模板重载,C++需要有一个良好的策略,去选择调用哪一个函数定义(尤其是多个参数时),这个过程称为重载解析。

(这个过程将会非常复杂,但愿不要遇到一定要写这种代码的时候。)

大致步骤#
Ⅰ.创建候选函数列表(其中包含与候选函数相同名称的函数和模板函数)。

Ⅱ.使用候选函数列表创建可行函数列表(要求参数数目正确,为此有一个隐式类型转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。例如,使用float参数的函数调用可以将该参数转换为double类型,从而与double匹配,而模板函数可以为float类型生成一个函数实例)。

Ⅲ.确定是否有最佳的可行函数(如果有则调用,没有则报错)。

我们以只有一个参数的函数为例:

1 may('B'); // 函数调用
2
3 /以下是一系列候选函数声明/
4 void may(int);    // #1
5 float may(float, float = 3);        // #2
6 void may(char);     // #3
7 char may(const char );        // #4
8 char may(const char &);        // #5
9 template void may(const T &); // #6
10 template void may(T *); // #7

这些函数声明都会进入函数列表(因为名称相同),接下来考虑特征标(参数数量与类型),不考虑返回值。其中#4和#7不可行,因为整数无法隐式类型转换为指针类型。#6可用来生成具体化,其中T被替换为char类型,此时还剩下5个可行的函数(#1、#2、#3、#5、#6)。如果此时只剩下一个,那么任何一个都可以正确使用。

接下来,到了择优环节。这一步主要考量的是函数调用参数与可行的候选函数的参数匹配所需要进行的转换。通常,从最佳到最差的顺序如下:

1、完全匹配,函数优于模板。

2、提升转换(例如,char和short自动转换为int,float自动转换为double)。

3、标准转换(例如,int转换为char,long转换为double)。

4、用户定义的转换,如类声明中定义的转换。

在剩余的5个函数中,#1优于#2,因为char到int是提升转换,而char到float是标准转换(此时还剩#1,#3,#5,#6)。#3、#5、#6优于#1和#2,因为他们是完全匹配(还剩#3,#5,#6)。#3和#5优于#6,因为#6是模板(还剩#3和#5)。

这时,会出现两个问题,完全匹配到底是什么?如果有两个完全匹配(#3和#5)该怎么办?通常有两个完全匹配是一种错误,但这一规则有两个例外。

完全匹配和最佳匹配#
进行完全匹配时,C++允许某些“无关紧要的转换”,下表列出了这些转换——Type表示任意类型。例如,int到int &,注意,Type可以是char &这样的类型,因此,这些规则也包括char &到const char &的转换。

完全匹配允许的无关紧要的转换
从实参 到形参
Type Type &
Type & Type
Type[] * Type
Type(参数列表) Type(*)(参数列表)
Type const Type
Type volatile Type
Type * const Type
Type volatile Type
假设有如下代码:

1 struct blot {int a; char b[10]};
2 blot ink = {25, "spots"};
3 recycle(ink);
4
5 // 下面的原型完全匹配
6 void recycle(blot); // #1 blot to blot
7 void recycle(const blot);   // #2 blot to const blot
8 void recycle(blot &);     // #3 blot to blot &
9 void recycle(const blot &); // #4 blot to const blot &

如果有多个完全匹配的原型,则无法完成重载解析过程,如果没有最最佳的可行函数,编译器将报错。

然而,有这样的例外规则,首先,指向非const数据的指针和引用优先于非const指针和引用,在上例中,如果只定义了#3和#4,将选择#3,因为ink没有被声明为const,然而const和非const之间的区别只适用于指针和引用指向的数据,也就是说,如果只定义了#1和#2,将出现二义性错误。

一个完全匹配优于另一个的另一种情况是,其中一个是非模板函数而另一个不是,这种情况下,非模板函数将优先于模板函数(包括显式具体化)。

如果两个完全匹配的函数都是模板函数,则较具体的模板函数优先,这意味着显示具体化将优于模板隐式生成的具体化。

1 struct blot {int a; char b[10]};
2 template void recycle(Type t); // 模板
3 template <> void recycle(blot & t); // 显示具体化
4
5 blot ink = {25, "spots"};
6 recycle(ink); //使用显示具体化
术语“最具体”并不一定意味着显示具体化,而是指编译器推断使用哪种类型时执行的转换最少。例如:

1 struct blot {int a; char b[10]};
2 template void recycle(Type t); // #1
3 template void recycle(Type * t); // #2
4
5 blot ink = {25, "spots"};
6 recycle(&ink); // 使用#2,因为转换最少,#2被认为是更具体的
用于找出最具体的模板的规则被称为部分排序规则。

部分排序规则#

1 template
2 void show(T arr[], int n); // #1
3
4 template
5 void show(T * arr[], int n); // #2
6
7 struct debts
8 {
9 char name[50];
10 double amount;
11 };
12
13 ......
14
15 int things[6] = {13,31,103,301,310,130};
16 debts mr[3] =
17 {
18 {"aaa", 24.1},
19 {"bbb", 25.2},
20 {"ccc", 26.3}
21 };
22 double * pd[3];
23
24 for(int i=0; i<3; i++)
25 {
26 pd[i] = &mr[i].amount;
27 }
28
29 show(things, 6); // 使用#1
30 show(pd, 3);   // 使用#2

things是一个int数组,与#1匹配,其中T被替换为int。pd是一个double 数组,与#1匹配时,T被替换为double ,与#2匹配时,T被替换为double。在这两个模板中,#2更加具体,因为它做了特定的假设,数组内容是指针,因此被使用。如果将#2从程序中删除,那么使用#1,将显示出地址,而不是值。

总之,重载解析将寻找最匹配的函数,如果只存在一个这样的函数,则选择它;如果存在多个这样的函数,但其中只有一个非模板函数,则选择它;入伏哦存在多个合适的函数且都为模板函数,但其中只有一个函数比其他函数更具体,则选择它。其他情况(有多个非模板或模板函数,但没有一个比其他更具体,或根本不存在匹配的函数)均为错误。

创建自定义选择#
在有些情况下,可以引导编译器做出你希望的选择。

1 template
2 T lesser(T a, T b); // #1
3
4 int lesser(int a, int b); // #2
5
6 ......
7
8 int m = 20;
9 int n = -30;
10 double x = 15.5;
11 double y = 25.9;
12
13 lesser(m, n); // 使用#2
14 lesser(x, y); // 使用#1,T被转换为double类型
15 lesser<>(m, n); // <>提示编译器,使用模板函数,使用#1
16 lesser(x, y); // 显式实例化,将使用实例化后的函数x,y被强制转换为int类型

多个参数的函数#
将有多个参数的函数调用与有多个参数的原型进行匹配时,情况将非常复杂。编译器必须考虑所有参数的匹配情况。如果找到比其他可行函数都合适的函数,则选择该函数。一个函数要比其他函数都合适,其所有参数的匹配程度都必须不必其他函数差,同时至少有一个参数的匹配程度比其他函数高。

作者: Dylan~

出处:https://www.cnblogs.com/Dylan7/p/12826456.html

相关文章
|
15天前
|
存储 C++ 容器
C++STL(标准模板库)处理学习应用案例
【4月更文挑战第8天】使用C++ STL,通过`std:vector`存储整数数组 `{5, 3, 1, 4, 2}`,然后利用`std::sort`进行排序,输出排序后序列:`std:vector<int> numbers; numbers = {5, 3, 1, 4, 2}; std:sort(numbers.begin(), numbers.end()); for (int number : numbers) { std::cout << number << " "; }`
17 2
|
26天前
|
编译器 C++
C++入门指南:10分钟带你快速了解模板究竟是什么(建议收藏!!)
C++入门指南:10分钟带你快速了解模板究竟是什么(建议收藏!!)
27 0
|
29天前
|
安全 算法 编译器
【C++ 泛型编程 进阶篇】深入探究C++模板参数推导:从基础到高级
【C++ 泛型编程 进阶篇】深入探究C++模板参数推导:从基础到高级
245 3
|
29天前
|
存储 算法 编译器
【C++ TypeName用法 】掌握C++中的TypeName:模板编程的瑞士军刀
【C++ TypeName用法 】掌握C++中的TypeName:模板编程的瑞士军刀
237 0
|
29天前
|
安全 算法 C++
【C++泛型编程 进阶篇】模板返回值的优雅处理(二)
【C++泛型编程 进阶篇】模板返回值的优雅处理
32 0
|
29天前
|
设计模式 程序员 C++
【C++ 泛型编程 高级篇】C++模板元编程:使用模板特化 灵活提取嵌套类型与多容器兼容性
【C++ 泛型编程 高级篇】C++模板元编程:使用模板特化 灵活提取嵌套类型与多容器兼容性
253 2
|
28天前
|
算法 C++ 开发者
【C++运算符重载】深入理解C++中的流运算符 >>和<<重载
【C++运算符重载】深入理解C++中的流运算符 >>和<<重载
36 0
|
11天前
|
编译器 C语言 C++
【C++初阶(九)】C++模版(初阶)----函数模版与类模版
【C++初阶(九)】C++模版(初阶)----函数模版与类模版
18 0
|
15天前
|
程序员 C++
C++语言模板学习应用案例
C++模板实现通用代码,以适应多种数据类型。示例展示了一个计算两数之和的模板函数`add&lt;T&gt;`,可处理整数和浮点数。在`main`函数中,展示了对`add`模板的调用,分别计算整数和浮点数的和,输出结果。
11 2
|
22天前
|
存储 缓存 C++
C++链表常用的函数编写(增查删改)内附完整程序
C++链表常用的函数编写(增查删改)内附完整程序

热门文章

最新文章