本文共 13726 字,大约阅读时间需要 45 分钟。
对于函数重载,函数模板,函数模板重载, C++需要一个良好的策略。来决定为函数调用使用哪一个函数定义。尤其是有多个参数时,这个过程称为重载解析。
第一步,创建候选函数列表,其中包含与被调用函数的名称相同的函数和模板函数。
第二步,使用候选参数列表创建可行函数列表。这些都是参数数目正确的函数。为此有一个隐式转换序列。其中包括实参类型与相应的形参类型完全匹配的情况。
第三步,确定是否有最佳的可行参数,如果有,则使用它,否则该函数调用出错!
char c = 'B';func(c);
void func(int); //#1float func(float,float = 3); //#2void func(char); //#3char * func(const char *); //#4char func(const char &); //#5templatevoid func(const T &); //#6template void func(T *); //#7
只考虑特征标,而不考虑返回类型。其中的两个候选函数(#4和#7)不可行,因为整数类型不能式地转换(即没有显式强制类型转换)为指针类型。剩余的一个模板可用来生成具体化,其中T被替换为char类型。这样剩下5个可行的函数,其中的每一个函数,如果它是声明的唯一一个函数,都可以被使用。
接下来,编译器必须确定哪个可行函数是最佳的。它查看为使函数调用参数与可行的候选函数的参数匹配所需要进行的转换。通常,从最佳到最差的顺序如下所述。#include/* author:梦悦foundation 公众号:梦悦foundation 可以在公众号获得源码和详细的图文笔记*/using namespace std;void func(int); //#1float func(float,float = 3); //#2void func(char); //#3char * func(const char *); //#4char func(const char &); //#5template void func(const T &); //#6template void func(T *); //#7void 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.cppcomplie_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] templatevoid func(const T &); //#6 ^~~~meng-yue@ubuntu:~/MengYue/c++/function/04$
这种分析引出了两个问题。什么是完全匹配?如果两个函数(如#3和#5)都完全匹配,将如何办呢? 通常,有两个函数完全匹配是一种错误,但这一规则有两个例外。显然,我们需要对这一点做更深入的探讨。
进行完全匹配时,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/* author:梦悦foundation 公众号:梦悦foundation 可以在公众号获得源码和详细的图文笔记*/using namespace std;struct blot { int a; char b[10];};void recyle(blot b); //#1void recyle(const blot b);//#2 void recyle(blot &b); //#3void recyle(const blot &b); //#4blot 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.cppexact_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 ^~~~~~
exact_match01.cpp
#include/* author:梦悦foundation 公众号:梦悦foundation 可以在公众号获得源码和详细的图文笔记*/using namespace std;struct blot { int a; char b[10];};void recyle(blot b); //#1void recyle(const blot b);//#2 //void recyle(blot &b); //#3//void recyle(const blot &b); //#4blot 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.cppexact_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 这两个函数没什么区别。
exact_match02.cpp
#include/* 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); //#3void recyle(const blot &b); //#4blot 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];};templatevoid recycle (Type t);// templatetemplate <> void recycle (blot & t); // specialization for blotblot ink =(25,"spots"):recycle(ink); // use specialization
术语“最具体(most specialized)”并不一定意味着显式具体化,而是指编译器推断使用哪种类型时执行的转换最少。例如,请看下面两个模板:
templatevoid recyle(Type t); //#1template 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 被认为是更加具体的,因为他需要进行的转换更少。 用于找出最具体的模板的规则被称为函数模板的 部分排序规则 partial_sort.cpp
#include/* author:梦悦foundation 公众号:梦悦foundation 可以在公众号获得源码和详细的图文笔记*/template void ShowAray(T array[], int n);//#1template void ShowAray(T * array[], int n);//#2using 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 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 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]:2ShowAray(T array[], int n)1 2ShowAray(T * array[], int n)1 2---------------结束--->公众号:梦悦foundation---------------meng-yue@ubuntu:~/MengYue/c++/function/04$
当调用 ShowAray(piArray, 2);
,, 模板 #2 更加具体,所以会调用 #2
简而言之,重载解析将寻找最匹配的函数。如果只存在一个这样的函数,则选择它;如果存在多个这样的函数,但其中只有一个是非模板函数,则选择该函数;如果存在多个适合的函数,且它们都为模板函数,但其中有一个函数比其他函数更具体,则选择该函数。如果有多个同样合适的非模板函数或模板函数,但没有一个函数比其他函数更具体,则函数调用将是不确定的,因此是错误的;当然,如果不存在匹配的函数,则也是错误。
在有些情况下,可通过编写合适的函数调用,引导编译器做出您希望的选择。
// choices.cpp -- choosing a template#include/* author:梦悦foundation 公众号:梦悦foundation 可以在公众号获得源码和详细的图文笔记*/using namespace std;template 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 (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-10add(T a, T b) // #141.4add(T a, T b) // #1-10add(T a, T b) // #140---------------结束--->公众号:梦悦foundation---------------meng-yue@ubuntu:~/MengYue/c++/function/04$
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
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的原因所在。 templatevoid ft(T1 x, T2 y){ ??type xpy = x + y;}
xpy应该是什么类型呢?
没有办法确定。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();
templatevoid ft(T1 x, T2 y){ decltype(x+y) xpy = x + y;}
decltype比这些实例演示要复杂一些,为确定类型,编译器必须遍历一个核对表,假设有如下声明:
decltype(expression) var
; 则核对表的简化版如下: double x = 5.5;double y = 7.9;double &rx = x;const double *pd;decltype(x) w; //w is type doubledecltype(rx) u = y; // u is type double &;decltype(pd) v; // v is type const double *
long demo(int a);decltype(demo(3)) m; // m is type long
注意:并不会实际调用函数,编译器通过查看函数的原型来获悉返回类型,而无需实际调用函数。
decltype.cpp// choices.cpp -- choosing a template#include/* 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$
double xx = 4.4;decltype((xx)) r2 = xx; // r2 is double &decltype(xx) w = xx; //w is dowble
int j = 3;int &k = j;int &n = j;decltype(j+6) i1; // i1 type intdecltype(100L) i2; //i2 type longdecltype(k+n) i3; // i3 type int
虽然k和n都是 引用,但表达式 k+n不是引用,他是两个int的和,因此类型为int
如果需要多次声明,可以结合typedef和decltypetemplatevoid ft(T1 x, T2 y){ typedef decltype(x+y) xytype; xytype xpy = x + y; xytype arr[10]; xytype & rxy = arr[2];}
有一个相关问题是decltype本身无法解决的
template??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指定返回类型,如下所示
templateauto gt(T1 x, T2y) -> decltype(x + y){ return x + y;}
代码资料路径
转载地址:http://aeyci.baihongyu.com/