想象一下,我有以下功能:
#include <iostream>
class A{ };
class B{ };
void foo(A&& a){ std::cout << "A&&" << std::endl; };
void foo(A& a){ std::cout << "A&" << std::endl; };
void foo(B& b, B& bb){ std::cout << "B&, B&" << std::endl; };
void foo(B& b){ std::cout << "B&" << std::endl; };
void foo(const A& a){ std::cout << "const A&" << std::endl; };
A a;
int main()
{
foo(a);
}
当我们调用函数foo(a)
时,候选函数集是什么?标准是说如下:
13.3.1.1.1调用命名函数[over.call.func]
在不合格的函数调用中,名称不符合 - &gt;要么 。 运算符,具有更通用的primary-expression形式。该 在后面的函数调用的上下文中查找名称 函数调用中的名称查找的常规规则(3.4)。
[...]
参数列表与调用
中的表达式列表相同
因此,候选函数将是除foo
之外的所有foo(B&, B&)
s(参数数量不同)。是吗?
答案 0 :(得分:1)
来自[over.call.func]:
在函数调用的上下文中查找名称,遵循函数调用中的名称查找的常规规则(3.4)。由该查找找到的函数声明构成了该集合 候选职能。
这只是不合格的查找。所以候选函数是任何名为foo
的函数。也就是说,所有这些:
void foo(A&& );
void foo(A& );
void foo(B& , B& );
void foo(B& );
void foo(const A& );
参数的数量是否匹配或任何转换都是可能的并不重要 - 第一步只是名称查找。这就是为什么这个词是候选功能的原因。这些都是候选人,我们还没有排除任何东西。
单独,我们确定参数列表。这是你引用的第二个片段,它的全部内容如下:
由于名称查找的规则,候选函数集完全由(1)组成 非成员函数或(2)完全是某些类T的成员函数。在情况(1)中,参数列表 与调用中的表达式列表相同。
我们在这里遇到案件1。所以在这种情况下,我们有5个候选函数和a
的参数列表。
答案 1 :(得分:1)
所有列出的功能都是候选功能。问题中引用的段落解释了为什么在跳过的部分:
在以下函数调用的上下文中查找名称 函数调用中名称查找的常规规则。功能 该查找找到的声明构成候选集 功能
如果没有冒险进入名称查找的奇妙世界,您可以从名称中推断出,如果找到了您的foo
函数之一,那么一切都将成为。
当编译器确定可行功能集时,更有趣的部分在此过程之后开始。我将介绍如何选择一个函数,以便更好地理解该过程。我鼓励你一起阅读,看看我遗漏了什么。我使用的是N4140。
我们将从§13.3.2/ 2中的第一点开始:
如果列表中有m个参数,则所有具有m个参数的候选函数都是可行的。
这排除了void foo(B& b, B& bb)
。没有带椭圆的函数或带有默认参数的多个参数,所以我们将跳过这些函数并转到§13.3.2/ 3:
第二,要使F成为可行的函数,每个都应存在 参数一个转换该参数的隐式转换序列 到F的相应参数。如果参数有参考 类型,隐式转换序列包括操作 绑定引用,以及左值引用的事实 非const不能绑定到rvalue和rvalue引用 不能绑定左值会影响函数的可行性。
从a
到B&
没有隐式转换序列,A&&
无法绑定到a
。这排除了void foo(B& b)
和void foo(A&& a)
。
现在我们归结为:
void foo(A& a)
void foo(const A& a)
时间继续进行重载决策,§13.3。我们有两个隐式转换序列:一个用于将a
转换为A&
,另一个用于将a
转换为const A&
。如果其中一个比另一个好(扰流板:它是),那么将选择该功能。
这些都属于§13.3.3.1.4,参考绑定。
当引用类型的参数直接绑定(8.5.3)为a时 参数表达式,隐式转换序列是标识 转换,除非参数表达式的类型是a 派生类的参数类型,在这种情况下是隐式的 转换序列是派生到基础的转换。
前往§8.5.3/ 4和/ 5:
给定类型“cv1 T1”和“cv2 T2”,“cv1 T1”与参考相关 “cv2 T2”,如果T1与T2的类型相同,或者T1是T2的基类。 如果T1为“cv1 T1”,则与“cv2 T2”引用兼容 与T2和cv1相关的引用与cv-qualification相同,或 比cv2更高的cv资格。
...
对类型“cv1 T1”的引用由类型的表达式初始化 “cv2 T2”如下:(5.1) - 如果引用是左值引用 和初始化表达式(5.1.1) - 是一个左值(但不是一个 比特字段),“cv1 T1”与“cv2 T2”参考兼容 ...
在除了最后一个之外的所有情况下(即创建和初始化a 暂时从初始化表达式),引用据说 直接绑定到初始化表达式。
由此我们得出结论,两个隐式转换序列都是身份转换。最后,我们将这些排在§13.3.3.2/ 3中:
两个相同形式的隐式转换序列 除非下列之一,否则无法区分转换序列 规则适用:
- 标准转换顺序S1更好 转换序列比标准转换序列S2 if
- S1和S2是引用绑定,以及类型 引用引用是相同的类型,除了顶级cv限定符, 并且S2引用的引用所引用的类型更多 cv-qualified比S1初始化的引用类型 指。
如果我们将a
设为A&
为S1而a
设为const A&
为S2,我们会看到S2更符合cv,因此此标准为已完成,S1是更好的转换顺序。
总之,void foo(A& a)
赢得重载决议并将被调用。