【问题标题】:Overload resolution and arrays: which function should be called?重载分辨率和数组:应该调用哪个函数?
【发布时间】:2011-07-17 20:59:32
【问题描述】:

考虑以下程序:

#include <cstddef>
#include <cstdio>

void f(char const*&&)      { std::puts("char const*&&");      } // (1)
void f(char const* const&) { std::puts("char const* const&"); } // (2)

template <std::size_t N>
void f(char const (&)[N])  { std::puts("char const(&)[N]");   } // (3)

int main()
{
    const char data[] = "a";
    f(data);
}

应该调用哪个f?为什么?

三个编译器的最新发布版本对这个问题的答案存在分歧:

  • (1) 在使用 g++ 4.5.2 编译程序时调用
  • (2) 在使用 Visual C++ 2010 SP1 编译程序时调用
  • (3) 在使用 Clang 3.0 (trunk 127530) 编译程序时调用

重载决议规则在不同的 C++0x 草案中是否发生了重大变化?或者,这些编译器中的两个真的完全错误吗?根据最新的 C++0x 草案选择哪个重载是正确的重载?

【问题讨论】:

  • 我尝试阅读最新的 C++0x 草案 N3242 的第 13 条(“重载”),但这让我很头疼。
  • 我很确定 gcc 做错了。即使所有三个候选者都是模板,gcc 仍然更喜欢转换为指针而不是完全匹配的数组引用。 ideone.com/7UXag
  • @Ben:在阅读第 13 条之前,我会同意你的看法:在表格中,数组到指针的转换被列为精确匹配,这意味着 (2) 是在这里正确选择,因为 (3) 是一个模板,但这也意味着在您的示例中,所有三个重载都是模板,至少 (2) 和 (3) 之间应该有歧义。 :-(
  • 不是答案,但我认为 (2) 是完全匹配的,不是吗?
  • @James:这甚至不是正确的部分,AFAICT。由于其中一个候选函数是模板函数,因此第 14.8.3 节进行管理。

标签: c++ arrays reference c++11 overload-resolution


【解决方案1】:

首先,所有三个的转换序列是相同的,除了前两个,有一个左值转换(左值到右值转换),但是它不用于排序转换序列。这三个都是完全匹配的(函数模板特化的参数类型为char const(&amp;)[2])。

如果您在13.3.3.2p3 处迭代规则,您将在此段停止

S1 和 S2 是引用绑定 (8.5.3),两者都没有引用非静态成员函数的隐式对象参数,声明时没有 ref-qualifier,S1 将右值引用绑定到右值,S2 绑定左值参考。

如果需要将右值引用绑定到左值,则无法形成转换序列,规范在 13.3.3.1.4p3 中说。如果您查看 8.5.3p5 最后一个项目符号的引用绑定是如何工作的,它将从数组 lvalue 中创建一个 char const* 类型的临时(我认为他们的意思是 rvalue 临时)并将引用绑定到那个暂时的。因此,我认为(1) 优于(2)(1)(3) 也一样,虽然我们不需要这个,因为 (3) 是一个模板,所以在平局中,我们会再次选择 (1)

n3225 中,他们更改了引用绑定规则,以便右值引用可以绑定到作为左值的初始化表达式,只要引用将绑定到右值(可能通过之前正确转换初始化程序创建)。这可能会影响 Visual C++ 的处理,此处可能不是最新的。

我不确定clang。即使它会忽略(1),它也会在(2)(3) 之间打成平手,并且需要选择(2),因为它不是模板。


我认为 8.5.3p5 的最后一个项目符号令人困惑,因为它说“否则为临时类型......”。目前尚不清楚该临时值是被 13.3.3.1.4p3 视为左值还是右值,这意味着我不确定根据规范的确切内容,以下内容应该如何真正表现

void f(int &);
void f(int &&);

int main() {
  int n = 0;
  f(n);
}

如果我们假设第 13 条将临时值视为右值,那么我们将右值 ref 绑定到第二个函数中的右值和第一个函数中的左值。因此,我们将选择第二个函数,然后通过 8.5.3p5 最后一个项目符号进行诊断,因为 T1T2 与引用相关。如果我们假设第 13 条将临时值视为左值,那么以下内容将不起作用

void f(int &&);
int main() {
  f(0);
}

因为我们会将右值 ref 绑定到第 13 条中的左值,这将使函数不可行。如果我们将“将右值引用绑定到左值”解释为引用初始化表达式而不是绑定到的最终表达式,我们将不会接受以下内容

void f(float &&);
int main() {
  int n = 0;
  f(n);
}

然而,这从 n3225 开始有效。所以似乎有些混乱 - 我就此向委员会发送了 DR。

【讨论】:

  • 非常有趣。遗憾的是,数组到指针的转换仍然是完全匹配的。能够专门匹配数组会很好(另一方面,我唯一想做的就是在这里回答问题,所以......也许它不是那么有用:-P)。非常感谢您的分析以及 DR 和 Clang 错误报告。我将跟进 Visual C++,看看他们的想法(因为 N3225 中的规则发生了变化,这确实解释了为什么 Visual C++ 的行为不同)。
【解决方案2】:

我声称 #3 是符合标准的编译器选择的函数。

(1) 比 (2) 好,因为“如果 S1 和 S2 是引用绑定 (8.5.3) 并且两者都不引用 a 的隐式对象参数,则标准转换序列 S1 比标准转换序列 S2 更好没有引用限定符声明的非静态成员函数,S1 将右值引用绑定到右值,S2 绑定左值引用。"

(3) 比 (1) 和 (2) 都好,因为它是恒等转换(其他是精确匹配转换),并且“如果 S1 是标准转换序列 S1 是比标准转换序列 S2 更好的转换序列S2 的适当子序列(比较 13.3.3.1.1 定义的规范形式的转换序列,不包括任何左值转换;恒等转换序列被认为是任何非恒等转换序列的子序列)"

仅当两种转换都不是更好的“或者,如果不是……”时,才考虑模板与非模板。

但奇怪的是,Comeau 更喜欢 (2) 而不是 (3)。此测试用例编译失败:

#include <cstddef>
#include <cstdio>

// (1) removed because Comeau doesn't support rvalue-references yet
char f(char const* const&) { std::puts("char const* const&"); return 0; } // (2)

template <std::size_t N>
int f(char const (&)[N])  { std::puts("char const(&)[N]"); return 0; } // (3)

int main()
{
    const char data[] = "a";
    switch (0) {
       case sizeof(char):
           break;
       case sizeof(f(data)):
           break;
    }
}

【讨论】:

【解决方案3】:

这是从标准(草案 3225)收集 sn-ps 的社区 wiki 答案。

第 13.3.3 节“最佳可行功能”[over.match.best]

  1. 如下定义ICSi(F):

    • 如果F 是一个静态成员函数,ICS1(F) 被定义为 ICS1(F) 既不优于也不差于 ICS1(G) 用于任何功能G,并且对称地,ICS1(G) 既不比 ICS1(F) 好也不差;否则,

    • 让 ICSi(F) 表示将列表中的第 i 个参数转换为 可行函数F的第i个参数的类型。 13.3.3.1 定义了隐式转换序列和 13.3.3.2 定义了一个隐式转换序列比另一个更好或更差的转换序列意味着什么。

    鉴于这些定义,如果对于所有参数 i,ICSi(F1) 不是更差的转换序列,则可行函数 F1 被定义为比另一个可行函数 F2 更好的函数比ICSi(F2),然后

    • 对于某些参数j,ICSj(F1) 是比 ICSj(F2) 更好的转换序列

    或者,如果不是这样,

    • 上下文是通过用户定义的转换(参见 8.5、13.3.1.5 和 13.3.1.6)和从 F1 的返回类型到目标类型(即实体被初始化)是一个比从F2的返回类型到目标类型的标准转换序列更好的转换序列

    或者,如果不是这样,

    • F1 是非模板函数,F2 是函数模板特化

    或者,如果不是这样,

    • F1F2 是函数模板特化,F1 的函数模板更特化 根据 14.5.6.2 中描述的部分排序规则,而不是 F2 的模板。
  2. 如果恰好有一个可行函数比所有其他可行函数更好,那么它就是重载决议选择的那个;否则调用格式不正确。

第 13.3.3.1.4 节“引用绑定”[over.ics.ref]

  1. 当引用类型的参数直接绑定 (8.5.3) 到参数表达式时,隐式转换 序列是恒等转换,除非参数表达式的类型是 参数类型,在这种情况下,隐式转换序列是派生到基础的转换 (13.3.3.1)。如果参数直接绑定到将转换函数应用于 参数表达式,隐式转换序列是用户定义的转换序列 (13.3.3.1.2),第二个标准转换序列要么是单位转换,要么是转换函数 返回一个类型的实体,该类型是参数类型的派生类,即派生到基的转换。

  2. 当引用类型的参数不直接绑定到参数表达式时,转换顺序 是将参数表达式转换为引用的基础类型所需的一种 到 13.3.3.1。从概念上讲,这个转换序列对应于复制初始化一个临时的 带有参数表达式的基础类型。顶级 cv 限定的任何差异都包含在 初始化本身并不构成转换。

第 13.3.3.2 节“对隐式转换序列进行排名”[over.ics.rank]

  1. 13.3.3.2 定义了基于关系的隐式转换序列的部分排序更好的转换 顺序和更好的转换。如果这些规则将隐式转换序列S1 定义为更好 转换序列比S2,那么S2 也是比S1 更差的转换序列。如果 转换序列S1 不比转换序列S2S1S2 好也不差 是无法区分的转换序列。

  2. 在比较隐式转换序列的基本形式时(定义见 13.3.3.1)

    • 标准转换序列 (13.3.3.1.1) 是比用户定义的转换序列或省略号转换序列更好的转换序列,并且

    • 用户定义的转换序列 (13.3.3.1.2) 是比省略号转换序列 (13.3.3.1.3) 更好的转换序列。

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

    • 标准转换序列S1是比标准转换序列S2更好的转换序列如果

      • S1 是 S2 的适当子序列(比较 13.3.3.1.1 定义的规范形式的转换序列,不包括任何左值转换;恒等转换序列被认为是任何非恒等转换序列的子序列)

      或者,如果不是这样,

      • S1 的排名优于S2 的排名,或者S1S2 的排名相同,可以通过下面段落中的规则进行区分

      或者,如果不是这样,

      • S1S2 仅在它们的限定转换上不同,并分别产生相似的类型 T1T2 (4.4),并且类型 T1 的 cv 限定签名是 cv 限定的适当子集T2 类型的签名。

      或者,如果不是这样,

      • S1S2 是引用绑定 (8.5.3),两者都没有引用 a 的隐式对象参数 没有引用限定符声明的非静态成员函数,并且S1 将右值引用绑定到 一个右值,S2 绑定一个左值引用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-13
    • 1970-01-01
    • 2012-09-26
    • 1970-01-01
    • 2011-03-04
    • 1970-01-01
    • 2023-01-30
    • 1970-01-01
    相关资源
    最近更新 更多