文章目录
1. 编译器选择哪个函数版本?
对于函数重载,函数模板,函数模板重载, C++需要一个良好的策略。来决定为函数调用使用哪一个函数定义。尤其是有多个参数时,这个过程称为重载解析。
-
第一步,创建候选函数列表,其中包含与被调用函数的名称相同的函数和模板函数。
-
第二步,使用候选参数列表创建可行函数列表。这些都是参数数目正确的函数。为此有一个隐式转换序列。其中包括实参类型与相应的形参类型完全匹配的情况。
-
第三步,确定是否有最佳的可行参数,如果有,则使用它,否则该函数调用出错!
1.一个简单的例子描述匹配规则
char c = 'B';
func(c);
- 首先,编译器将寻找候选者,即名称为func()的函数和函数模板。然后寻找那些可以用一个参数调用的函数。例如,下面的函数符合要求,因为其名称与被调用的函数相同,且可只给它们传递一个参数:
void func(int); //#1
float func(float,float = 3); //#2
void func(char); //#3
char * func(const char *); //#4
char func(const char &); //#5
template <class T> void func(const T &); //#6
template <class T> void func(T *); //#7
只考虑特征标,而不考虑返回类型。其中的两个候选函数(#4和#7)不可行,因为整数类型不能式地转换(即没有显式强制类型转换)为指针类型。剩余的一个模板可用来生成具体化,其中T被替换为char类型。这样剩下5个可行的函数,其中的每一个函数,如果它是声明的唯一一个函数,都可以被使用。
接下来,编译器必须确定哪个可行函数是最佳的。它查看为使函数调用参数与可行的候选函数的参数匹配所需要进行的转换。通常,从最佳到最差的顺序如下所述。
- 完全匹配,但常规函数优先于模板。
- 提升转换(例如,char和shorts自动转换为int,float自动转换为double)。
- 标准转换(例如,int转换为char,long转换为double)。
- 用户定义的转换,如类声明中定义的转换。
例如,函数#1优于函数#2,因为char到int的转换是提升转换(参见第3章),而char到float的转换是标准转换(参见第3章)。
函数#3、函数#5和函数#6都优于函数#1和#2,因为它们都是完全匹配的。
#3和#5优于#6,因为#6函数是模板。
complie_rule.cpp
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using namespace std;
void func(int); //#1
float func(float,float = 3); //#2
void func(char); //#3
char * func(const char *); //#4
char func(const char &); //#5
template <class T> void func(const T &); //#6
template <class T> void func(T *); //#7
void demo(long double t);
int main(int argc, char * argv [ ])
{
char c = 'M';
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
func(c);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
上面的代码编译通过不了,因为匹配的函数太多了!
meng-yue@ubuntu:~/MengYue/c++/function/04$ g++ -o complie_rule complie_rule.cpp
complie_rule.cpp: In function ‘int main(int, char**)’:
complie_rule.cpp:23:8: error: call of overloaded ‘func(char&)’ is ambiguous
func(c);
^
complie_rule.cpp:9:6: note: candidate: void func(int)
void func(int); //#1
^~~~
complie_rule.cpp:10:7: note: candidate: float func(float, float)
float func(float,float = 3); //#2
^~~~
complie_rule.cpp:11:6: note: candidate: void func(char)
void func(char); //#3
^~~~
complie_rule.cpp:13:6: note: candidate: char func(const char&)
char func(const char &); //#5
^~~~
complie_rule.cpp:14:25: note: candidate: void func(const T&) [with T = char]
template <class T> void func(const T &); //#6
^~~~
meng-yue@ubuntu:~/MengYue/c++/function/04$
这种分析引出了两个问题。什么是完全匹配?如果两个函数(如#3和#5)都完全匹配,将如何办呢? 通常,有两个函数完全匹配是一种错误,但这一规则有两个例外。显然,我们需要对这一点做更深入的探讨。
2 .完全匹配和最佳匹配
进行完全匹配时,C++允许某些“无关紧要的转换”。表8.1列出了这些转换——Type表示任意类型。例如,int实参与int&形参完全匹配。注意,Type可以是char&这样的类型,因此这些规则包括从char &到const char &的转换。
正如您预期的,如果有多个匹配的原型,则编译器将无法完成重载解析过程;如果没有最佳的可行函数,则编译器将生成一条错误消息,该消息可能会使用诸如“ambiguous(二义性)”这样的词语。
然而,有时候,即使两个函数都完全匹配,仍可完成重载解析。
首先,指向非const数据的指针和引用优先与非const指针和引用参数匹配。也就是说,在recycle()示例中,如果只定义了函数#3和#4是完全匹配的,则将选择#3,因为ink没有被声明为const。然而,const和非const之间的区别只适用于指针和引用指向的数据。也就是说,如果只定义了#1和#2,则将出现二义性错误。
exact_match.cpp
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using namespace std;
struct blot {
int a;
char b[10];
};
void recyle(blot b); //#1
void recyle(const blot b);//#2
void recyle(blot &b); //#3
void recyle(const blot &b); //#4
blot ink = {25, "meng-yue"};
int main(int argc, char * argv [ ])
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
recyle(ink);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
运行结果!
meng-yue@ubuntu:~/MengYue/c++/function/04$ g++ -o exact_match exact_match.cpp
exact_match.cpp: In function ‘int main(int, char**)’:
exact_match.cpp:29:12: error: call of overloaded ‘recyle(blot&)’ is ambiguous
recyle(ink);
^
exact_match.cpp:16:6: note: candidate: void recyle(blot)
void recyle(const blot b);//#2
^~~~~~
exact_match.cpp:17:6: note: candidate: void recyle(blot&)
void recyle(blot &b); //#3
^~~~~~
exact_match.cpp:18:6: note: candidate: void recyle(const blot&)
void recyle(const blot &b); //#4
^~~~~~
3. 只是定义了 #1 , #2
exact_match01.cpp
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using namespace std;
struct blot {
int a;
char b[10];
};
void recyle(blot b); //#1
void recyle(const blot b);//#2
//void recyle(blot &b); //#3
//void recyle(const blot &b); //#4
blot ink = {25, "meng-yue"};
int main(int argc, char * argv [ ])
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
recyle(ink);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
void recyle(blot b) //#1
{
cout << "recyle(blot b) " << endl;
}
//void recyle(const blot b)//#2
//{
//
// cout << "recyle(const blot b)" << endl;
//}
//void recyle(blot &b) //#3
//{
// cout << "recyle(blot &b)" << endl;
//}
//void recyle(const blot &b) //#4
//{
// cout << "recyle(const blot &b)" << endl;
//}
运行结果,这个地方只是声明是不会报错的!
meng-yue@ubuntu:~/MengYue/c++/function/04$ ./exact_match01
---------------开始--->公众号:梦悦foundation---------------
recyle(blot b)
---------------结束--->公众号:梦悦foundation---------------
假设把下面的代码注释给开开
//void recyle(const blot b)//#2
//{
//
// cout << "recyle(const blot b)" << endl;
//}
运行结果
meng-yue@ubuntu:~/MengYue/c++/function/04$ g++ -o exact_match01 exact_match01.cpp
exact_match01.cpp: In function ‘void recyle(blot)’:
exact_match01.cpp:41:6: error: redefinition of ‘void recyle(blot)’
void recyle(const blot b)//#2
^~~~~~
exact_match01.cpp:36:6: note: ‘void recyle(blot)’ previously defined here
void recyle(blot b) //#1
^~~~~~
meng-yue@ubuntu:~/MengYue/c++/function/04$
报错了,提示说重定义了,编译器认为 #1 和 #2 这两个函数没什么区别。
4. 只是定义 #3 , #4
exact_match02.cpp
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using namespace std;
struct blot {
int a;
char b[10];
};
//void recyle(blot b); //#1
//void recyle(const blot b);//#2
void recyle(blot &b); //#3
void recyle(const blot &b); //#4
blot ink = {25, "meng-yue"};
int main(int argc, char * argv [ ])
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
recyle(ink);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
//void recyle(blot b) //#1
//{
// cout << "recyle(blot b) " << endl;
//}
//void recyle(const blot b)//#2
//{
//
// cout << "recyle(const blot b)" << endl;
//}
void recyle(blot &b) //#3
{
cout << "recyle(blot &b)" << endl;
}
void recyle(const blot &b) //#4
{
cout << "recyle(const blot &b)" << endl;
}
运行结果:
meng-yue@ubuntu:~/MengYue/c++/function/04$ ./exact_match02
---------------开始--->公众号:梦悦foundation---------------
recyle(blot &b)
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/function/04$
一个完全匹配优于另一个的另一种情况是,其中一个是非模板函数,而另一个不是。在这种情况下,非模板函数将优先于模板函数(包括显式具体化)。
如果两个完全匹配的函数都是模板函数,则较具体的模板函数优先。例如,这意味着显式具体化将优于使用模板隐式生成的具体化:
struct blot {int a; char b[10];};
template <class Type> void recycle (Type t);// template
template <> void recycle<blot> (blot & t); // specialization for blot
blot ink =(25,"spots"):
recycle(ink); // use specialization
术语“最具体(most specialized)”并不一定意味着显式具体化,而是指编译器推断使用哪种类型时执行的转换最少。例如,请看下面两个模板:
template <class Type> void recyle(Type t); //#1
template <class Type> void recyle(Type *t) //#2
假设包含这些模板的程序也包含如下代码:
struct blot
{
int a;
char b[10];
};
blot ink = {25, "meng-yue"};
recyle(&ink);
recyle(&ink)
调用与 #1 模板匹配, 匹配时将 Type 解释成 blot *
recyle(&ink)
调用也与 #2 模板匹配,这次 Type 被解释成 blot
#1 —》recyle<blot *>(blot *)
#2----》recyle<blot>(blot *)
#2 被认为是更加具体的,因为他需要进行的转换更少。
用于找出最具体的模板的规则被称为函数模板的 部分排序规则
2. 部分排序规则
partial_sort.cpp
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
template <typename T>
void ShowAray(T array[], int n);//#1
template <typename T>
void ShowAray(T * array[], int n);//#2
using namespace std;
int main(int argc, char * argv [ ])
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
int iArray[2] = {1 ,2};
int *piArray[2];
piArray[0] = &iArray[0];
piArray[1] = &iArray[1];
cout << "iArray[0]:" << iArray[0] << ", iArray[1]:" << iArray[1] << endl;
cout << "*piArray[0]:" << *piArray[0] << ", *piArray[1]:" << *piArray[1] << endl;
ShowAray(iArray, 2);
ShowAray(piArray, 2);
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
return 0;
}
template <typename T>
void ShowAray(T array[], int n)
{
cout << "ShowAray(T array[], int n)" << endl;
for (int i = 0; i < n; i ++) {
cout << array[i] << " ";
}
cout << endl;
}
template <typename T>
void ShowAray(T * array[], int n)
{
cout << "ShowAray(T * array[], int n)" << endl;
for (int i = 0; i < n; i ++) {
cout << *array[i] << " ";
}
cout << endl;
}
运行结果
meng-yue@ubuntu:~/MengYue/c++/function/04$ ./partial_sort
---------------开始--->公众号:梦悦foundation---------------
iArray[0]:1, iArray[1]:2
*piArray[0]:1, *piArray[1]:2
ShowAray(T array[], int n)
1 2
ShowAray(T * array[], int n)
1 2
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/function/04$
当调用 ShowAray(piArray, 2);
,, 模板 #2 更加具体,所以会调用 #2
简而言之,重载解析将寻找最匹配的函数。如果只存在一个这样的函数,则选择它;如果存在多个这样的函数,但其中只有一个是非模板函数,则选择该函数;如果存在多个适合的函数,且它们都为模板函数,但其中有一个函数比其他函数更具体,则选择该函数。如果有多个同样合适的非模板函数或模板函数,但没有一个函数比其他函数更具体,则函数调用将是不确定的,因此是错误的;当然,如果不存在匹配的函数,则也是错误。
3.自己选择
在有些情况下,可通过编写合适的函数调用,引导编译器做出您希望的选择。
// choices.cpp -- choosing a template
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using namespace std;
template<class T>
T add(T a, T b) // #1
{
cout << "add(T a, T b) // #1 " << endl;
T c = a + b;
return c;
}
int add (int a, int b) // #2
{
cout << "add (int a, int b) // #2" << endl;
int c = a + b;
return c;
}
int main()
{
int iM = 20;
int iN = -30;
double dX = 15.5;
double dY = 25.9;
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
cout << add(iM, iN) << endl; // use #2
cout << add(dX, dY) << endl; // use #1 with double
cout << add<>(iM, iN) << endl; // use #1 with int
cout << add<int>(dX, dY) << endl; // use #1 with int
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
// cin.get();
return 0;
}
运行结果
meng-yue@ubuntu:~/MengYue/c++/function/04$ ./choices
---------------开始--->公众号:梦悦foundation---------------
add (int a, int b) // #2
-10
add(T a, T b) // #1
41.4
add(T a, T b) // #1
-10
add(T a, T b) // #1
40
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/function/04$
cout << add(iM, iN) << endl; // use #2
这个函数调用与模板函数和非模板函数都匹配,因此选择非模板函数,返回-10。
接下来,下述语句中的函数调用与模板匹配(T为double):
cout << add(dX, dY) << endl;// use #1 with double
现在来看下面的语句:
cout << add<>(iM,iN) << endl; // use #1 with int
add<>(iM, iN) 中的<> 指出编译器应选择模板函数,而不是非模板函数;编译器注意到实参的类型为int,因此使用int替代T对模板进行实例化。
最后,请看下面的语句:
cout << lesser<int>(x,y) << endl;// use #l with int
这条语句要求进行显式实例化(使用int替代T),将使用显式实例化得到的函数。dX和dY的值将被强制转换为int,该函数返回一个int值,这就是程序显示40而不是41.4的原因所在。
4.函数模板的发展
1. 是什么类型?
template <class T1, class T2>
void ft(T1 x, T2 y)
{
??type xpy = x + y;
}
xpy应该是什么类型呢?
没有办法确定。
2. 关键字decltype
c++ 11 新增关键字 decltype提供了解决方案,可这样使用关键字
int x;
decltype(x) y;// 让x的类型和 y一样
给decltype提供的参数可以是表达式,因此在前面的模板函数ft()中,可使用下面的代码!
decltype(x+y) xpy; xpy = x + y;
另一种方法是将两条语句合二为一:
decltype(x+y) xpy = x + y;
因此,可以这样修复前面的模板函数ft();
template <class T1, class T2>
void ft(T1 x, T2 y)
{
decltype(x+y) xpy = x + y;
}
decltype比这些实例演示要复杂一些,为确定类型,编译器必须遍历一个核对表,假设有如下声明:
decltype(expression) var
;
则核对表的简化版如下:
- 如果expression是一个没有用括号括起来的标识符,则var的类型与该标识符的类型相同,包括const等限定符。
double x = 5.5;
double y = 7.9;
double &rx = x;
const double *pd;
decltype(x) w; //w is type double
decltype(rx) u = y; // u is type double &;
decltype(pd) v; // v is type const double *
- 如果expression是一个函数调用,则var的类型与函数的返回类型相同
long demo(int a);
decltype(demo(3)) m; // m is type long
注意:并不会实际调用函数,编译器通过查看函数的原型来获悉返回类型,而无需实际调用函数。
decltype.cpp
// choices.cpp -- choosing a template
#include <iostream>
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
using namespace std;
long demo(int m)
{
cout << "demo(int m)" << endl;
}
int main()
{
cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
decltype(demo(3)) i = 4;
cout << "i = " << i << endl;
cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
// cin.get();
return 0;
}
运行结果, 可以看出来demo函数并没有被调用!
meng-yue@ubuntu:~/MengYue/c++/function/04$ ./decltype
---------------开始--->公众号:梦悦foundation---------------
i = 4
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/function/04$
- 如果expression是一个左值,则var为指向其类型的引用。这好像意味着前面的w应该为引用类型,因为x是一个左值,得用括号括起来,才是引用的类型!看下面的这个例子!
得用括号括起来,才是引用的类型!看下面的这个例子!
double xx = 4.4;
decltype((xx)) r2 = xx; // r2 is double &
decltype(xx) w = xx; //w is dowble
- 如果前面的条件都不满足,则var的类型与expression 的类型相同
int j = 3;
int &k = j;
int &n = j;
decltype(j+6) i1; // i1 type int
decltype(100L) i2; //i2 type long
decltype(k+n) i3; // i3 type int
虽然k和n都是 引用,但表达式 k+n不是引用,他是两个int的和,因此类型为int
如果需要多次声明,可以结合typedef和decltype
template<class T1, calss T2>
void ft(T1 x, T2 y)
{
typedef decltype(x+y) xytype;
xytype xpy = x + y;
xytype arr[10];
xytype & rxy = arr[2];
}
3. 另外一种函数声明语法(c++后置返回类型)
有一个相关问题是decltype本身无法解决的
template <class T1, class T2>
??type gt(T1 x, T2 y)
{
return x + y;
}
返回类型没有办法确定
为此c++新增了一种声明和定义函数的语法,
double h(int x, float y);
使用新增的语法可以编写成这样:
auto h(int x, float y) -> double;
这将返回类型移到了参数声明后面, ->被称为后置返回类型,其中的auto是一个占位符,表示后置返回类型提供的类型,这是c++ 11 给auto新增的一种角色,这种语法也可以用于函数定义。
auto h(int x, float y) -> double
{
//函数实现
}
通过结合使用这种语法和decltype,便可以给gt指定返回类型,如下所示
template <class T1, class T2>
auto gt(T1 x, T2y) -> decltype(x + y)
{
return x + y;
}
5. 去哪获取笔记和详细的资料
代码资料路径