7

我以为我非常了解名称查找(在观看了几个有关它的视频并阅读了很多内容之后),但我只是遇到了这种情况:

#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& ),这似乎是唯一可能的候选者。

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

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

(不幸的是,通过阅读标准文档很难理解名称查找,所以我需要专家确认)

4

5 回答 5

6

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

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

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

3.4.1 [basic.lookup.unqual] 第 8 段列出了搜索名称的上下文,甚至还有一个可以准确回答您问题的示例。Test在封闭命名空间之前搜索的范围,正如第 1 段所说的“一旦找到名称的声明,名称查找就结束”

于 2013-05-22T17:55:41.463 回答
4

语言越是努力为结构找到有效的解释,错字或其他此类错误就越有可能导致编译器找到有效但错误的含义。编译器假设 iffoo在某个范围内定义,并且该范围内的代码使用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 会假设它打算编写局部变量。就个人而言,我不喜欢这两种假设。“确切地说出你的意思”的原则向我建议,编译器应该要求程序员说this.x=4;or X=4;,这两者都不可能被解读为具有错误的含义。

于 2013-05-22T16:56:38.187 回答
2

这根本不是查找问题。关键是在重载决议开始之前查找完成。当编译器看到do_something它执行查找以弄清楚它的含义时,它发现它是一个函数,这反过来激活 ADL 以查找其他潜在的重载。然后查找完成并开始重载解析。当重载解决失败时。

于 2013-05-22T18:33:41.530 回答
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();
}

(A) 不编译,因为 [basic.lookup.unqual]:“一旦找到名称的声明,名称查找就结束”

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

但是,有 [basic.lookup.argdep]/3:

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

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

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

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

于 2013-05-22T16:56:01.953 回答
1

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

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

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

于 2013-05-22T16:33:18.227 回答