这是有效的C++代码吗?

23

我有以下的代码,基本上是这样的:

class foo {
  public:
    void method();
};

void foo::foo::method() { }

在foo::method的定义前面,我不小心多加了一个foo::。在使用g++(ver 4.2.3)编译时,这段代码没有警告,但在使用Visual Studio 2005时会出错。我没有一个名为foo的命名空间。

哪个编译器是正确的?


1
我将那段代码原封不动地复制到了一个单文件项目中,并添加了一个空的主函数。这在gcc 4.3.3下实际上可以编译。 - hookenz
1
我可以确认,在g ++上编译此代码时没有任何警告(我已在3.4.5 mingw上尝试过),而且没有任何名称空间或其他。另一方面,MSVC2008拒绝了它。非常奇怪。 - Tamas Czinege
1
奇怪的是,使用i686-apple-darwin8-g++-4.0.1编译器,我可以添加任意数量的“foo ::”,即使使用“-Wall -Wextra”编译选项,也没有警告。 - Adam Rosenfield
1
已确认在 Linux 上的 g++ 4.3.3 上也能正常工作。 - Karl Voigtland
2
哈哈,第一个在g++/gcc中发现bug的人! - jkeys
显示剩余8条评论
4个回答

25
如果我正确理解标准,g++是正确的,而VS是错误的。
ISO-IEC 14882-2003(E),§9.2 类(第153页):类名在被看到后立即插入它所声明的作用域中。该类名也被插入到类本身的作用域中;这被称为注入类名。为了进行访问检查,注入的类名被视为公共成员名。
接下来,关于实际名称查找规则,保留以下信息尤其有用:
ISO-IEC 14882-2003(E),§3.4-3 名称查找(第29页):对于名称隐藏和查找目的,类的注入类名(第9条)也被认为是该类的成员。
如果不是这样的话,那将会很奇怪,考虑到第9.2节文本的最后部分。但正如litb所评论的那样,这使我们确信g++正在正确地解释标准。没有疑问了。

1
有趣,这是否解释了 foo::foo::foo::method 也能正常工作的事实? - MikeT
2
不完全是这样,不是的。我认为可以在3.4.3.1 - 合格名称查找中找到该描述:类或命名空间成员的名称可以在指定其类或命名空间的嵌套名称限定符之后的::作用域分辨符(5.1)之后引用。在寻找::作用域分辨符之前的名称时,对象、函数和枚举器名称将被忽略。如果找到的名称不是类名(第9条)或命名空间名(7.3.1),则程序是非法的。 - Alexandre Bell
特别注意的是,在 :: 域解析运算符之前查找名称时,对象、函数和枚举器名称将被忽略。 - Alexandre Bell
2
另外一句引用:“对于名称隐藏和查找,类的注入类名(第9条)也被认为是该类的成员。”来自3.4/3。在类中查找是根据成员名称定义的 - 因此需要这句话才能使问题中的情况有效。请注意,Comeau在拒绝有效代码struct A { void f() { A::A a; } };时是错误的 :) 如果您在答案中包含该引用,我会给您点赞 xD - Johannes Schaub - litb
当然,litb。但是不需要+1。我发现9.2在许多方面都很自解释。但我同意3.4/3使其更加明显。等我在我的标准副本上查找一下... - Alexandre Bell
顺便提一下,Comeau 是正确的,在我上面的代码中它会拒绝 "A::A a;"。这是因为 3.4.3.1/1a 规定,那段代码中的 "A::A" 指的是构造函数,而不是类名。这个问题相当棘手... - Johannes Schaub - litb

11

Krugar 在这里给出了正确的答案。每次被找到的名称都是注入的类名。

以下是一个示例,至少说明编译器添加注入类名的一个原因:

namespace NS
{
  class B
  {
  // injected name B    // #1
  public:
    void foo ();
  };

  int i;                // #2
}

class B                 // #3
{
public:
  void foo ();
};


int i;                  // #4

class A :: NS::B
{
public:
  void bar ()
  {
    ++i;           // Lookup for 'i' searches scope of
                   // 'A', then in base 'NS::B' and
                   // finally in '::'.  Finds #4

    B & b = *this; // Lookup for 'B' searches scope of 'A'
                   // then in base 'NS::B' and finds #1
                   // the injected name 'B'.

  }
};

如果没有注入的名称,当前查找规则最终会到达'A'的封闭范围,并找到'::B'而不是'NS::B'。因此,在A中希望引用基类时,我们需要到处使用"NS::B"。

注入的名称在模板中也会被使用,在类模板内部,注入的名称提供了模板名称和类型之间的映射:

template <typename T>
class A
{
// First injected name 'A<T>'
// Additional injected name 'A' maps to 'A<T>'

public:
  void foo ()
  {
    // '::A' here is the template name
    // 'A' is the type 'A<T>'
    // 'A<T>' is also the type 'A<T>'
  }
};

1

Comeau online毫无问题地接受它,所以它要么有效,要么是我在近十年来发现的Comeau中的第二个bug。


0

你包含的其他模块中有命名空间foo吗(而你只是不知道)?否则,这是不正确的。我不确定为什么g++会允许这样。


实际上,g++ 允许它。我刚刚测试过。 - hookenz
我不确定为什么g++允许了这个。 - jkeys

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