候选功能集

时间:2015-07-02 14:11:23

标签: c++ overloading language-lawyer

想象一下,我有以下功能:

#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(参数数量不同)。是吗?

2 个答案:

答案 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引用   不能绑定左值会影响函数的可行性。

aB&没有隐式转换序列,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)赢得重载决议并将被调用。