【问题标题】:Unexpected ambiguity of surrogate call functions in C++C++ 中代理调用函数的意外歧义
【发布时间】:2014-04-11 02:47:26
【问题描述】:

在以下代码中,clang 和 EDG 诊断出不明确的函数调用,而 gcc 和 Visual Studio 接受该代码。

struct s
{
    typedef void(*F)();
    operator F();       //#1
    operator F() const; //#2
};

void test(s& p)
{
    p(); //ambiguous function call with clang/EDG; gcc/VS call #1
}

根据 C++ 标准草案 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3797.pdf) 第 13.3.1.1.2 节 2 说;

具有唯一名称 call-function 并具有 R call-function (conversion-type-id F, P1 a1, ... ,Pn an) { return F (a1,... ,一个); } 也被视为候选函数。

在上面的代码中,这似乎意味着正在考虑两个调用函数定义(每个转换函数一个),但是两个调用函数具有相同的签名(因此存在歧义),因为转换运算符的 cv 限定符不似乎在调用函数签名中考虑到了。

我希望 #1 会像 gcc 和 Visual Studio 一样被调用。因此,如果 clang/EDG 拒绝上述代码是正确的,那么有人可以解释一下为什么标准规定在这种情况下应该有歧义以及 代码从代理调用函数的属性中受益?谁是对的:clang(3.5)/EDG(310) 还是 gcc (4.8.2)/VS(2013)?

【问题讨论】:

  • 问题似乎仅限于代理调用函数的重载解决方案;在解析void foo( void(*)() ); foo(p); 时,clang++ 明确选择第一个转换函数:live example(考虑到 [over.call.object]/4,这很奇怪。)
  • 你用的是哪个版本的gcc?从 gcc46 升级到 gcc47 后,我遇到了一个相关问题,这种情况与您的情况类似,但在先前编译的重载模板函数上导致错误,直到我重命名其中一个。升级后我还有更多需要解决的警告。
  • @andrew-mcdonnell 它是 gcc 4.8.2。我还编辑了问题以包含我尝试过的其他编译器的版本号。
  • 非常类似于stackoverflow.com/q/22064519/476681。我认为 gcc 和 msvc 是正确的。

标签: c++ gcc clang language-lawyer


【解决方案1】:

Clang 和 EDG 是对的。

这是它的工作原理。该标准说(与您的报价相同的来源):

另外,对于每一个在T形式中声明的非显式转换函数

operator conversion-type-id () attribute-specifier-seq[opt] cv-qualifier ;

其中 [在您的示例中满足各种条件],具有唯一名称 call-function 并具有以下形式的 代理调用函数

R call-function ( conversion-type-id F, P1 a1, ... ,Pn an) { return F (a1, ... ,an); }

也被视为候选函数。 [对继承的转换做同样的事情]^128

脚注指出,这可能会产生多个具有无法区分签名的代理,如果这些代理没有被明显更好的候选者取代,则该调用是模棱两可的。

按照这个方案,你的类型有两个转换运算符,产生两个代理:

// for operator F();
void call-function-1(void (*F)()) { return F(); }
// for operator F() const;
void call-function-2(void (*F)()) { return F(); }

您的示例不包含任何其他候选人。

然后编译器执行重载决议。因为这两个调用函数的签名是相同的,所以它将对两者使用相同的转换序列 - 特别是,它将使用转换函数的非常量重载在这两种情况下!所以无法区分这两个函数,调用有歧义。

理解这一点的关键是,在将对象传递给代理项时实际使用的转换不必使用为代理项生成的转换函数!

我可以看到 GCC 和 MSVC 在这里得出错误答案的两种方式。

选项 1 是他们看到两个具有相同签名的代理,然后以某种方式将它们融合为一个。

选项 2 更有可能是他们认为,“嘿,我们不需要在这里为对象进行昂贵的转换搜索,我们已经知道它将使用生成代理的转换函数为了”。这听起来像是一种优化,除了在这种极端情况下,这种假设是错误的。无论如何,通过将转换与源转换函数联系起来,其中一个代理使用 identity-user-identity 作为对象的转换顺序,而另一个使用 const-user-identity,使情况变得更糟。

【讨论】:

  • 不确定...您从标准中省略的各种条件包括“其中 cv-qualifier 与 cv-qualification 相同或大于 cv-qualification, cv",即cv对象类型的cv-qualification。而且由于 OP 中的对象不是const,我认为重载#2 不符合要求,不应考虑。
  • cv是对象表达式的限定符,为空。 cv-qualifier 是转换函数的限定条件,在一种情况下为空(相同),const 在另一种情况下(更大)为空。两者都有效。
  • 嗯 [over.call.object]/4 中有一条注释说“派生代理调用函数的转换函数将在该参数的转换序列中使用 [隐含对象parameter] 因为它将隐含的对象参数转换为第一个参数所需的适当函数指针或引用。”那么这是错的吗?
  • 是的,该注释似乎是错误的。由于它只是一个注释,它可能会被视为编辑缺陷。
  • 关于歧义的脚注 129 引用了关于考虑在基类中声明的转换函数的句子(在第 13.3.1.1.2.2 节中),但示例中没有基类。
猜你喜欢
  • 1970-01-01
  • 2011-10-14
  • 2014-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多