嵌套类奇怪的函数查找:周围的类函数隐藏全局函数

时间:2014-12-10 13:30:28

标签: c++ gcc argument-dependent-lookup

我有以下简化代码

namespace Namespace
{
int foo() { return 1; }

class Class
{
public:
    int foo() const { return 2; }
    class Nested {
    public:
        Nested()
        {
            cout << foo() << endl;
        }
    };
};
}

我收到了这个错误:

  

错误:无法调用成员函数'int Namespace :: Class :: foo()const'   没有对象:

 cout << foo() << endl;
         ^^^^^

似乎编译器选择非静态int Namespace::Class::foo() const而不是全局函数int Namespace::foo()

但是如何能够在没有对象的情况下调用来自其他类的非静态函数呢? Nested object无法访问周围的Class object - 毕竟这不是Java。

我仔细阅读了overload resolution from cppreference我无法找到这种行为的理由。我更怀疑这是gcc错误。

  • 您能指出对此行为负责的语言规则吗?
  • 你是如何处理这些问题的?

[UPDATE]

第二个问题的答案。解决方法很简单,需要告诉编译器存在这样的全局函数:

        Nested()
        {
            using Namespace::foo; //< workaround: inform compiler such function exists
            cout << foo() << endl;
        }
顺便说一下,解决方法是否正确?有没有更好的解决方案?

3 个答案:

答案 0 :(得分:5)

  

我仔细阅读了来自cppreference的重载解析我无法找到这种行为的基本原理。你能指出导致这种行为的语言规则吗?

重载解析过程选择最佳可行功能之前,会在名称查找阶段生成一组初始候选项。换句话说,应在Name lookup部分搜索预期的行为,而不是Overload resolution部分。

C ++标准中描述了非限定名称的名称查找过程:

§3.4.1[basic.lookup.unqual] / p8:

  

在函数的 declarator-id 之后或大括号中的类X的成员函数(9.3)的定义中使用的名称类X 的非静态数据成员(9.2)的-or-equal-initializer 应以下列方式之一声明:

     

- 在使用它的块中或在封闭块(6.3)中使用之前,或

     

- 应该是X类成员或者是X(10.2)基类的成员,或者

     

- 如果X是类Y(9.7)的嵌套类,则应为Y 的成员,或者是{的成员Y的基类   (这个查找依次应用于Y的封闭类,从最里面的封闭类开始,或者[...]

并且只有在仍未找到的情况下:

  

- 如果X是名称空间N的成员,或是属于N 成员的类的嵌套类,或者是本地类或属于N成员的函数的本地类中的嵌套类,在使用名称之前,在命名空间N 或其中一个{ {1}}包含名称空间。

由于名称查找在找到名称后立即结束(§3.4.1[basic.lookup.unqual] / p1):

  

在3.4.1中列出的所有情况下,在每个相应类别中列出的顺序中搜索范围;一旦找到名称的声明,名称查找就会结束。

在您遇到N时,不会搜索其他范围。


  

解决方法很简单,需要告诉编译器存在这样的全局函数:

int foo() const { return 2; }
     

这种解决方法是否正确?

§7.3.3[namespace.udecl] / p1:

  

using-declaration 在声明区域中引入了一个名称, using-declaration 出现在该声明区域中。

§3.3.1[basic.scope.declarative] / p1:

  

程序文本的某些部分引入了每个名称,称为声明区域,这是该名称​​有效的程序的最大部分,即其中该名称可用作非限定名称以引用同一实体。

使用 using-declaration 引入名称会影响非限定名称查找,使其在第一步找到该函数,即该名称变为声明

  

- 在使用它的块中或在封闭块(6.3)中使用之前


  

有没有更好的解决方案?

在引用某个命名空间作用域中的函数时,可以使用限定名称,明确指出要引用的符号:

using Namespace::foo; //< workaround: inform compiler such function exists

答案 1 :(得分:4)

这确实是关于Name Lookup而不是Overload Resolution。

事实上,正如标准规定的那样,N3376(强调我的)§3.4.1/ 1

  

在3.4.1中列出的所有情况下,在每个相应类别中列出的顺序中搜索范围;一旦找到名称的声明,名称查找就会结束。如果没有找到声明,程序就会格式不正确。

事实上,只要找到声明,查找就会停止。

然后,您可以在§3.4.1/ 8 中找到有关您正在处理的案例的更多信息,该案例处理成员函数定义中类中使用的名称,尤其是此部分:

  

如果X是类Y(9.7)的嵌套类,则应该是Y的成员,或者应该是Y的基类的成员(此查找依次应用于Y的封闭类,从最内层封闭开始类)

如果没有找到,请附上名称空间。

在您的情况下,这意味着Namespace::Class::foo()是查找过程中找到的第一个名称,一旦找到名称就会停止,因此甚至不会考虑Namespace::foo()

标准给出的示例说明了查找路径:

class B { };
namespace M {
    namespace N {
        class X : public B {
            void f(); 
        };
    } 
}
void M::N::X::f() {
    i = 16;
}
  

搜索以下范围以获取i:

的声明      

1)M :: N :: X :: f的最外层块范围,在使用i // 2之前,类M :: N :: X的范围

     

3)M :: N :: X基类B的范围

     

4)命名空间M :: N

的范围      

5)命名空间M的范围

     

6)全局范围,在M :: N :: X :: f

的定义之前

至于你如何处理这个问题,你必须对这个电话进行限定,即

cout << Namespace::foo() << endl;

以便编译器通过限定查找选择所需的函数。

答案 2 :(得分:2)

Piotr S.和JBL解释了你问题的原因。

恕我直言,更简单的解决方案是使用函数的限定名称:

public:
    Nested()
    {
        cout << Namespace::foo() << endl;
    }