C++11中运算符查找规则

15

N3337,“C++编程语言标准”的工作草案,在第13.3.1.2条第10页给出了以下示例:

struct A { };
void operator + (A, A);
struct B {
  void operator + (B);
  void f ();
};
A a;
void B::f() {
  operator+ (a,a);   // error: global operator hidden by member
  a + a;             // OK: calls global operator+
}
然而,这只是一个注释:

注意:在表达式中操作符的查找规则与在函数调用中的操作符函数名称的查找规则不同,如下面的示例所示:

我的问题是标准文件中在哪里写明了必须发生这种情况,而不只是带有示例的注释?
据我所知,根据第13.3.1.2条款第2页的规定,操作符表达式会转换为操作符函数调用。那么为什么以及如何出现上述示例中的差异?
编辑:
经过研究,我认为可能已经忽略了相同条款中的第3页和第6页,它们一起说明查找操作符时考虑全局候选项和成员候选项是相等的(因此,查找规则与注释所述不同)。然而,我的这个问题源于这个示例,在GCC 4.8和Clang中以相同的方式编译。
struct X {};  struct Y {};

void operator+(X, X) { }
void operator+(X, Y) { }

void test() {
  void operator+(X, X);
  X x; Y y;

  x + x;  // OK
  x + y;  // OK

  operator+(x, y);  // error
  operator+(x, x);  // OK
}

当直接调用操作符函数时,块作用域声明为什么会出现阴影效应,但在调用操作符表达式时却不会出现?

下面是来自GCC的错误信息:

operators-main-ss.cpp: In function ‘void test()’:
operators-main-ss.cpp:13:17: error: could not convert ‘y’ from ‘Y’ to ‘X’
   operator+(x, y);  // error
                 ^

这里是来自Clang的内容:

operators-main-ss.cpp:13:16: error: no viable conversion from 'Y' to 'X'
  operator+(x, y);  // error
               ^
operators-main-ss.cpp:1:8: note: candidate constructor (the implicit copy constructor) not viable: no
      known conversion from 'Y' to 'const X &' for 1st argument;
struct X {};  struct Y {};
       ^
operators-main-ss.cpp:7:22: note: passing argument to parameter here
  void operator+(X, X);
                     ^

编译器在一个情况下阻止声明块遮蔽全局名称,而在另一种情况下则没有。这样做是否正确?


5
就好像你故意想让我头痛一样。 - Lightness Races in Orbit
3个回答

4

你的原问题:

你的结论是正确的。

operator+(a, a); 是一个简单的函数调用,因此编译器按标准顺序搜索潜在匹配项,找到成员函数后不再继续查找。成员函数不匹配,表达式无效。

a + a; 使用略有不同的规则,如13.3.1.2 p3所定义。首先从 A::operator+(a) 产生一组可能的重载。然后从标准未限定查找中产生可能的重载列表,但忽略成员函数。接下来创建内置操作符列表。这三个列表随后被用来决定最佳匹配项。通常,如果未限定查找找到了某些东西,就会在第一步停止查找,这就是为什么它通常会失效,而在这里可以工作的原因。

问题第二部分:

理论上应该是相同的。没有涉及成员函数和内置操作符也无关紧要,因此它们都应该导致标准未限定查找。我在标准中找不到任何暗示它们应该有任何不同的地方。除了"存在"之外,我在标准中几乎没有看到块范围函数声明的具体规定。它们基本上只是C语言的遗产。我也没有在标准中找到任何特定的规则,说明这是否应该工作。标准非常强烈地暗示 x + yoperator+(x, y) 应该同时工作或同时失效,因为它们都使用相同的名称查找方法。

我还没有找到嵌套函数声明的有效用例。


1
在开始研究这个问题之前,我也不太了解块级作用域声明。似乎可以使用它们来避免全局命名空间的污染,关闭 ADL 并显式选择在给定块中应调用哪些函数。否则,块级作用域声明似乎与非块级作用域声明相同。当然,不允许有块级定义。我不会提倡使用块级作用域声明,但这些是我能想到的可能情况,并且似乎无法通过其他方式实现。 - Marcin Zalewski
2
@Marcin Zalewski,我不确定他们如何避免命名空间污染,因为定义(和可能的链接)仍然在函数外部,并且限定名称、转换或重定向将解决选择问题,所以我仍然不认为有必要使用它们。话虽如此,我自认为对所有黑暗角落都了解得很清楚,所以我会一直关注这个问题。如果/当我发现更多信息时,我会更新答案。 - John5342
链接不需要来自同一文件。这类似于使用指令。您可以将一个放在块作用域之外,但如果您只需要它用于一个块作用域,那么可以直接将其放在其中。如果您使用限定名称,则仅选择一个函数,但是使用块作用域声明,您可以选择更多函数并在此选择上执行重载解析。再次说明,我实际上没有看到过这种用法,但是这些用法是我能想到的唯一用法。 - Marcin Zalewski
但在这种情况下,使用指令或重定向会起作用。即将新函数放入独立的命名空间中,并导入所需内容。将参数转换为更合适的类型也可以改变所选函数,而不限制于一个函数。我想这有点像C风格的强制转换。它只是一个向后兼容的功能,恰好提供了另一种完成相同事情的方式。 - John5342
顺便说一下,我还没有将你们的任何答案标记为已接受的答案,因为不同的编译器给出了至少3个不同的答案(请参见@JesseGood答案的评论)。我特别担心EDG前端之间行为的变化。新的前端要么做对了,要么与Commeau编译器中旧前端所做的相比引入了一个错误。也许我们仍然缺少标准的其他部分。或者EDG的人搞错了。希望我们能够搞清楚这个问题。 - Marcin Zalewski
显示剩余5条评论

1
编译器在一个情况下阻止声明遮蔽全局名称,而在另一个情况下不是这样,这种做法正确吗?
我得出结论,两个编译器都是错误的。我认为x + y;也应该失败。13.3.1.2p3清楚地说明了这一点:
“非成员候选集是根据表达式上下文中operator@的未限定查找结果确定的,根据未限定函数调用(3.4.2)的名称查找通常规则,但忽略所有成员函数。”
因此,在您的示例中,x + y;和operator+(x, y);之间不应有任何区别。Commeau在线生成以下代码错误:Commeau online
"ComeauTest.c", line 11: error: no operator "+" matches these operands
            operand types are: X + Y
    x + y;  // OK
      ^
"ComeauTest.c", line 13: error: no suitable user-defined conversion from "Y"
 to "X"
          exists
    operator+(x, y);  // error

很奇怪。我尝试了使用EDG前端的英特尔编译器,但是完全没有出现任何错误。现在我感到非常困惑。顺便说一句,下载英特尔编译器真的很麻烦。 - Marcin Zalewski
@MarcinZalewski:我刚用MSVC2012测试了这段代码。代码可以编译,但智能感知会出现与commeau相同的错误(非常奇怪)。 - Jesse Good
这是因为根据EDG集团的页面,MSVC使用EDG前端来进行智能感知,但不用于实际编译。此外,似乎MSVC必须使用比最新的英特尔编译器更新的EDG前端版本。 - Marcin Zalewski
@MarcinZalewski:谢谢,我不知道这个。我尝试查找使用的EDG版本,但找不到任何最近的信息。 - Jesse Good
那么现在我们应该信任哪个前端呢? :) 我倾向于选择最新版本的EDG,因为他们总是非常严格地遵守标准,但我没有看到标准中说他们做得对。对于Clang,这个讨论似乎与此有关。如果到现在为止还没有改变,那么Clang似乎无法处理这个问题。 - Marcin Zalewski
我收到了一些编译器作者的消息,他们确认你的分析是正确的,并且Commeau编译器确实做出了正确的事情。 - Marcin Zalewski

0

关于你的问题第二部分

这里是标准c++11中关于ADL $3.4.2的引用:

设X为未限定查找(3.4.1)产生的查找集,Y为参数依赖查找产生的查找集(定义如下)。如果X包含

  • 类成员的声明,或者
  • 不是using-declaration的块作用域函数声明,或者
  • 既不是函数也不是函数模板的声明

那么Y为空。

...

看起来这回答了你的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接