命名空间作用域中的运算符隐藏了全局作用域中的另一个运算符

13

这是编译器的bug吗?

template <typename T>
T& operator++(T& t)
{
    return t;
}

namespace asdf {

enum Foo { };
enum Bar { };

Foo& operator++(Foo& foo);

void fun()
{
    Bar bar;
    ++bar;
}

} // end namespace asdf

int main()
{
    return 0;
}

GCC 4.7的错误信息如下:
error: no match for 'operator++' in '++bar'
note: candidate is:
note: asdf::Foo& asdf::operator++(asdf::Foo&)
note: no known conversion for argument 1 from 'asdf::Bar' to 'asdf::Foo&'

如果您注释掉这一行,它就可以编译:

Foo& operator++(Foo& foo);

1
是的,它是 - Karthik T
3
不这样认为。VC++ 生成了相同的代码。 - SChepurin
1
@KarthikT:我不确定你链接的代码如何支持“是一个错误”的论点。 - Sebastian Mach
3个回答

14

不,那不是一个 Bug。有三组并行的运算符被考虑。成员运算符、非成员运算符和内置运算符。

非成员运算符通过正常的未限定名称查找和 ADL 查找,忽略所有类成员函数。因此全局运算符会被语法更接近的运算符隐藏(而介于两者之间的成员函数也不会隐藏其他的非成员函数)。

请注意,在名称查找1成功后才进行重载解析;在您的情况下,找到了名称为 operator++,但没有适当的重载。

如果 Bar 被全局声明,或者 namespace asdf 中的另一个运算符,ADL(在前一种情况下)或普通的未限定名称查找(在后一种情况下)将会拖出该运算符。


1: 重载解析 (...) 在名称查找成功后进行。(C++ 标准)


我不确定这是否足够详细。如果没有匹配,应该查找下一个封闭的命名空间吗? - Sebastian Mach
1
@phresnel:但是 operator++ 这个名称如何与 operator++ 不匹配?在名称查找期间,只有名称是相关的。 - Bart van Ingen Schenau
@BartvanIngenSchenau:我在挑衅litb添加这个细节(试图从提问者的角度思考问题),但现在,我不认为我可以点赞了。 - Sebastian Mach
@phresnel 随意通过编辑来完善答案。拥抱 - Johannes Schaub - litb

8
不,这不是编译器的错误。
对于表达式++bar,有两个名称查找被执行。
常规名称查找搜索封闭范围和命名空间,直到找到operator++的第一个出现。该搜索从内向外进行,因此全局命名空间最后被搜索。在寻找运算符函数时,成员函数被单独处理(并且不会停止此搜索)。
接下来启动参数相关的查找,并搜索与函数参数相关的其他类和命名空间(在本例中为operator++)。
在问题的示例中,常规查找找到asdf::operator++并停止查找。 参数相关的查找仅将asdf命名空间添加到要搜索的位置中,因为它是enum Bar的相关命名空间。因此,无法找到全局operator++。
您可以通过在namespace asdf中使用声明来找到全局operator++。

你描述的第一个要点没有考虑类作用域。也就是说,即使在类成员函数中使用运算符表达式或命名空间作用域运算符,在非 ADL 相关的命名空间作用域中仍然可以找到相同名称的成员运算符。 - Johannes Schaub - litb
@JohannesSchaub-litb:这是C++03和C++11之间的变化吗?因为我在C++03条款3.4.1 [basic.lookup.unqual]中找不到这样的异常。 - Bart van Ingen Schenau
1
规则已在13.3.1.2中指定 :-) - Johannes Schaub - litb
我正在不同编译器之间移植代码。编译器A可以接受这个问题中的代码(它显然包括用于重载解析的全局运算符)- 这应该被视为编译器的错误吗? - M.M
1
@MattMcNabb:是的,如果编译器A在其符合模式下接受此问题中的代码,则编译器A存在错误。 - Bart van Ingen Schenau

1

重载仅适用于在相同作用域中定义的名称。一旦编译器找到匹配的名称,它就不会查找外部作用域,即使找到的名称适用于无法使用的内容。这与运算符无关;如果代码以与使用operator++相同的方式使用函数名称,则会出现相同的错误。例如:

void f(int);

struct C {
void f(const C&);
void g() {
    f(3); // error: f(const C&) can't be called with argument 3
};

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