当内部范围不起作用时,为什么编译器不采用命名空间名称?

时间:2013-05-22 16:26:49

标签: c++ visual-c++ gcc c++11

我以为我很了解名字查询(在观看了几个关于它的视频并阅读了很多内容之后),但我只是遇到了这个案例:

#include <iostream>

namespace test{

  struct Id
  {};

  void do_something( const Id& ){  std::cout << "Hello, World!" << std::endl; }

  class Test
  {
  public:

    void do_something() { std::cout << "WTF!" << std::endl;  }

    void run()
    {
      Id id;
      do_something( id ); // doesn't compile
    }

  };

}

int main()
{
    test::Test my_test;
  my_test.run();
}

指向的行不编译(在GCC4.8和VC11U2上),因为它试图使用成员函数test::Test::do_something()而不是名称空间作用的test::do_something( const Id& ),这似乎是唯一可能的候选者。

显然,成员函数名称隐藏了命名空间范围的名称,这对我来说是令人惊讶的,因为我记得在其他上下文中使用几乎相似的代码而没有产生此问题(但最终条件可能会有很大不同)。

我的问题是:这些编译器是否符合标准?

(遗憾的是,通过阅读标准文档很难理解名称查找,因此我需要专家确认)

5 个答案:

答案 0 :(得分:6)

  

我的问题是:这些编译器是否符合标准?

是。在重载决策决定哪些函数可行候选(包括检查参数的数量)之前,编译器首先必须进行名称查找,以找到所有候选者,可行且不可行。在您的示例名称中,查找在成员do_something()后停止,因此重载解析永远不会有机会决定命名空间范围是否可行。

  

3.4.1 [basic.lookup.unqual] / 1:“在3.4.1中列出的所有情况下,搜索范围按照每个相应类别中列出的顺序搜索声明;名称查找尽快结束作为名称的声明。“

3.4.1 [basic.lookup.unqual]第8段列出了搜索名称的上下文,甚至还有一个能够准确回答你问题的例子。 Test的范围在封闭的命名空间之前被搜索,并且第1段说明“名称查找在名称”找到声明后立即结束。

答案 1 :(得分:4)

语言越是找不到构造的有效解释,错字或其他类似错误就越有可能导致编译器找到有效但错误的含义< / em>的。编译器假设如果在某个范围内定义了foo,并且该范围内的代码使用foo,程序员打算让代码使用范围内定义的foo。如果程序员试图用foo做一些内部范围定义不允许的事情,那么下列情况之一的几率是非常好的:

  • 程序员打算做一些其他稍微不同的操作,这在内部foo上是有效的。除非程序员指定,否则不能指望编译器知道程序员的意图。
  • 程序员打算执行指示的操作,而不是意识到内部foo不能支持它,因此必须找到内部foo的其他一些操作或操作序列。可以支持。同样,除非程序员指出如何正确使用foo,否则编译器无法生成良好的代码。
  • 程序员打算对外部foo进行有问题的操作,但拒绝明确说明。如果编译器想要猜测这是程序员的意思,它可以生成代码就行了。

只有当程序员的意图是#3时,编译器才可能生成符合预期行为的代码。然而,程序员真的打算#1或#2更有可能。如果编译器拒绝编译代码,即使假设#3会生成有效的代码,那么上述任何错误都将被找到并因此可以得到纠正。相比之下,如果编译器在可能的情况下假设为#3,那么如果程序员确实打算#1或#2问题在代码运行之前就不会出现,并且表现与设计相反。

顺便说一句,如果我有我的druthers,我会在.NET语言中将这个原则应用于区分大小写,不仅禁止以与定义不一致的方式编写任何标识符(如C#而不是vb)。 net),但是使用任何仅在内部范围内与上部/下部外壳不同的标识符。例如:

class foo
{
  int x;
  void bar()
  {
    int X=2;
    x=4; // ****
    return X;
  }
}

鉴于上面的代码,C#会猜测带有星号的行是为了写字段;给定类似的代码,vb.net会假设它是为了编写局部变量。就个人而言,我不喜欢这两种假设; &#34;准确地说出你的意思&#34;会告诉我编译器应该要求程序员说this.x=4;X=4;,这两者都不可能被认为具有错误的含义。

答案 2 :(得分:2)

这根本不是查询问题。关键点是查找在重载决策开始之前完成。当编译器看到do_something时,它执行查找以找出它意味着什么,它发现它是一个函数,反过来又激活ADL以发现其他潜在的过载。然后查找完成并开始重载解析。当重载解析失败时。

答案 3 :(得分:1)

编译器将收集所有“候选名称”,这些名称将来自同一范围,除非涉及ADL,然后尝试选择最佳匹配(如果有)。在任何情况下,失败的匹配都不会导致它尝试从备用范围中查找其他候选名称。

这与编译器首先执行重载解析非常类似,然后检查成员的公共/私有,看它是否实际可访问。

g ++有一个方便的-Wshadow选项来追捕阴影(我不确定它会特别警告这一点)。

答案 4 :(得分:1)

  

遗憾的是,通过阅读标准文档很难理解名称查找,因此我需要专家确认

我无论如何都不是专家,但这就是我如何理解标准的名称查找规则。

两个例子:

void foo(int);

namespace associated
{
    struct bee {};
    void flower(bee);
}

namespace bar
{
    void foo();
    void flower();

    void test()
    {
        foo(42);                   // (A)
        flower(associated::bee()); // (B)
    }
}

int main()
{
    bar::test();
}
由于[basic.lookup.unqual],

(A)无法编译:“一旦找到名称的声明,名称查找就会结束”

(B)因ADL而编译; associated是一个关联的命名空间。

然而,有[basic.lookup.argdep] / 3:

  

设X是由非限定查找(3.4.1)生成的查找集,并且让Y是由参数相关查找生成的查找集(定义如下)。如果X包含

     
      
  • 类成员的声明,或
  •   
  • 块范围函数声明,它不是using声明或
  •   
  • 既不是函数也不是函数模板的声明
  •   
     

然后Y为空。否则Y是在与参数类型相关联的名称空间中找到的声明集,如下所述。通过查找名称找到的声明集是X和Y的并集。

第一点适用于您的示例。因此,我认为是,拒绝您的示例的编译器符合标准。