【问题标题】:Set of candidate functions候选函数集
【发布时间】:2015-07-02 14:11:23
【问题描述】:

想象一下我有以下功能:

#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]

在不合格的函数调用中,名称不是由 -> 或 限定的。 运算符,并且具有更一般的主表达式形式。这 name 在函数调用的上下文中查找 函数调用中名称查找的常规规则 (3.4)。

[...]

参数列表与调用中的表达式列表相同

因此,候选函数将是除foo(B&amp;, B&amp;) 之外的所有foos(参数数量不同)。对吗?

【问题讨论】:

  • 你想要候选函数还是可行函数?根据您引用的段落,它们都是候选函数。
  • @chris 不,我想了解候选函数是什么。
  • 查看 13.3/2。它首先决定候选函数和参数列表,以便它可以将调用函数的所有不同方式统一到一个统一的候选列表中(例如,将成员函数和非成员函数放在同一个列表中)。然后它会做一些更明显的事情,比如删除参数数量不正确的那些,以获得可行函数的列表。然后在可行函数之间执行重载决议。

标签: c++ overloading language-lawyer


【解决方案1】:

来自[over.call.func]:

按照函数调用中名称查找的常规规则 (3.4) 在函数调用的上下文中查找名称。 由该查找找到的函数声明构成了一组 候选函数。

这只是不合格的查找。所以候选函数是任何名为foo 的函数。也就是说,所有这些:

void foo(A&& );
void foo(A& );
void foo(B& , B& );
void foo(B& );
void foo(const A& );

参数的数量是否匹配或是否可以进行任何转换都无关紧要 - 第一步只是名称查找。这就是为什么这个术语是 candidate 函数。这些都是候选人,我们还没有排除任何东西。

分开,我们确定参数列表。这是您引用的第二个片段,全文如下:

由于名称查找规则,候选函数集完全由 (1) 非成员函数或 (2) 完全是某个类 T 的成员函数。在情况 (1) 中,参数列表 与调用中的表达式列表相同。

我们在这里是案例 1。所以在这种情况下,我们有 5 个候选函数和一个 a 的参数列表。

【讨论】:

    【解决方案2】:

    所有列出的函数都是候选函数。问题中引用的段落解释了跳过部分的原因:

    在函数调用的上下文中查找名称 函数调用中名称查找的正常规则。功能 该查找找到的声明构成候选集 功能。

    无需冒险进入名称查找的奇妙世界,您可以从它的名称推断,如果找到您的 foo 函数之一,一切都会。

    更有趣的部分在这个过程之后开始,当编译器确定一组可行的函数时。我将介绍如何选择一个函数,以便更好地理解该过程。我鼓励你继续阅读,看看我遗漏了什么。我用的是 N4140。

    我们将从 §13.3.2/2 中的第一点开始:

    如果列表中有 m 个参数,则所有具有恰好 m 个参数的候选函数都是可行的。

    这排除了void foo(B&amp; b, B&amp; bb)。没有带有省略号或带有默认参数的超过 1 个参数的函数,因此我们将跳过这些并继续第 13.3.2/3 节:

    其次,为了使 F 成为一个可行的函数,每个函数都应该存在 参数 转换该参数的隐式转换序列 到F的对应参数。如果参数有引用 类型,隐式转换序列包括操作 绑定引用,以及左值引用的事实 非 const 不能绑定到右值和右值引用 不能绑定到左值会影响函数的可行性。

    没有从aB&amp; 的隐式转换序列,并且A&amp;&amp; 不能绑定到a。这排除了void foo(B&amp; b)void foo(A&amp;&amp; a)

    现在我们开始:

    void foo(A& a)
    void foo(const A& a)
    

    是时候讨论重载决议了,§13.3。我们有两个隐式转换序列:一个将a 转换为A&amp;,另一个将a 转换为const A&amp;。如果其中一个比另一个更好(剧透:确实如此),那么将选择该函数。

    这两个都属于 §13.3.3.1.4,引用绑定。

    当引用类型的参数直接绑定 (8.5.3) 到 参数表达式,隐式转换序列是恒等式 转换,除非参数表达式的类型是 参数类型的派生类,在这种情况下隐式 转换序列是派生到基的转换。

    转到 §8.5.3/4 和 /5:

    给定类型“cv1 T1”和“cv2 T2”,“cv1 T1”与引用相关 如果 T1 与 T2 的类型相同,或者 T1 是 T2 的基类,则为“cv2 T2”。 如果 T1 是,“cv1 T1”与“cv2 T2”引用兼容 与 T2 和 cv1 相关的参考与 cv 限定相同,或 比 cv2 更高的 cv 资格。

    对类型“cv1 T1”的引用由类型表达式初始化 “cv2 T2”如下: (5.1) — 如果引用是左值引用 初始化表达式 (5.1.1) — 是一个左值(但不是 位域),并且“cv1 T1”与“cv2 T2”引用兼容,

    在除最后一种情况外的所有情况下(即,创建和初始化一个 来自初始化表达式的临时),该引用被称为 直接绑定到初始化表达式。

    由此我们得出结论,两个隐式转换序列都是恒等转换。最后,我们在 §13.3.3.2/3 中对它们进行排名:

    两个相同形式的隐式转换序列是 不可区分的转换序列,除非以下之一 规则适用:

    • 标准转换序列 S1 更好 转换序列比标准转换序列 S2 if
      • S1 和 S2 是引用绑定,而 除了顶级 cv 限定符之外,引用引用是相同的类型, 并且 S2 初始化的引用所指的类型更多 cv-qualified 比由 S1 初始化的引用的类型 参考。

    如果我们将aA&amp; 设为S1,将aconst A&amp; 设为S2,我们看到S2 更符合cv,所以这个标准得到满足,S1 是更好的转换序列.

    总之,void foo(A&amp; a) 赢得重载决议并将被调用。

    【讨论】:

      猜你喜欢
      • 2021-12-31
      • 1970-01-01
      • 1970-01-01
      • 2017-04-25
      • 2021-09-19
      • 2014-01-22
      • 1970-01-01
      • 1970-01-01
      • 2013-03-20
      相关资源
      最近更新 更多