带有前向声明的嵌套类在 clang++ 中导致错误,但在 g++ 上没有警告地通过

dat*_*ata 7 c++ g++ inner-classes language-lawyer clang++

我试图结合嵌套类和前向声明来保持代码清晰,即使它有一个非常复杂的类结构。前向声明允许我减少此处的缩进级别。

以下代码在 g++-9.3 和 clang++-10 上都可以很好地编译:

class A {
    public:
        class B;
};
class A::B {
    public:
        int foo=0;
};
Run Code Online (Sandbox Code Playgroud)

但是,当我在另一个类中嵌套执行相同的操作时,此构造在 g++ 上没有任何警告地工作,但在 clang++ 上失败:

class Outer {
   public:
      class A {
         public:
            class B;
      };
      class A::B {
         public:
            int foo=0;
      };
};
Run Code Online (Sandbox Code Playgroud)

clang++ 的失败是:

test.cpp:7:16: error: non-friend class member 'B' cannot have a qualified name
      class A::B {
            ~~~^
1 error generated.
Run Code Online (Sandbox Code Playgroud)

我想这在某种程度上是无效的代码,gcc 足够仁慈以正确解释?我意识到我可以直接在 A 类中移动类定义,但假设我想保留这个版本的前向声明。

dfr*_*fri 5

头名称包含嵌套名称说明符的类说明符不能出现在类中;仅在封闭的命名空间内

\n

来自[class]/11

\n
\n

如果类头名称 包含嵌套名称说明符,则该类说明符 应引用先前直接在嵌套名称说明符引用类或命名空间中声明的类,或者在该类的元素中声明的类。该命名空间的内联命名空间集(即不仅仅是由using-declaration继承或引入),并且类说明符应出现在包含先前声明的命名空间中。在这种情况下,定义的类头名称嵌套名称说明符不应以decltype-specifier开头。

\n
\n

在您的失败示例中,A::nested-name-specifierA::B { ... }class-specifier。标准

\n
\n

[...]类说明符应出现在包含先前声明的命名空间中。

\n
\n

没有实现。特别要注意的是,类说明符包含类头,类头又包含类头名称,类头名称又包含可选的嵌套名称说明符,以及(非可选)类-姓名。如果存在嵌套名称说明符,则 [class]/11 适用,在这种情况下(如上所述),允许使用类说明符的要求适用;特别是,不允许嵌套在类中。

\n

因此你的程序格式不正确;铿锵是对的。

\n
\n

我们可能会注意到,如果类说明符forB出现在命名空间范围(包含先前的类内声明),则Clang 接受该程序:

\n
class Outer {\n public:\n    class A {\n      public:\n        class B;\n    };\n};\n\nclass Outer::A::B {\n  public:\n    int foo=0;\n};\n
Run Code Online (Sandbox Code Playgroud)\n
\n

最后,请注意[class.nest]/3提到在声明它们的类的类中以及封闭名称空间的名称空间范围内定义嵌套类:

\n
\n

如果类X是在命名空间作用域中定义的,Y则可以在类中声明嵌套类X,然后在类的定义中进行定义X,或者稍后在包含类定义的命名空间作用域中进行定义X。[_\xe2\x80\x89示例:_

\n
 class E {   class I1;       // forward declaration of nested class  \n class I2;   class I1 { };   // definition of nested class }; class\n E::I2 { };  // definition of nested class\n
Run Code Online (Sandbox Code Playgroud)\n

\xe2\x80\x89 \xe2\x80\x94\xe2\x80\x89end 示例\xe2\x80\x89]

\n
\n

这允许你的第一个例子:

\n
\n
class A {\n  public:\n    class B;\n};\nclass A::B {\n  public:\n    int foo=0;\n};\n
Run Code Online (Sandbox Code Playgroud)\n
\n

被重构为

\n
class A {\n  public:\n    class B {\n      public:\n        int foo=0;\n    };\n};\n
Run Code Online (Sandbox Code Playgroud)\n

或者

\n
class A {\n  public:\n    class B;  // forward declaration\n    // ...\n    class B {\n      public:\n        int foo=0;\n    };\n};\n
Run Code Online (Sandbox Code Playgroud)\n

但这里与 [class]/11 没有冲突,它专门管理类头名称包含嵌套名称说明符时允许的语法。

\n


cig*_*ien 2

我认为代码是有效的,这是一个 clang bug。根据class.qual#1

如果限定 ID 的嵌套名称说明符指定一个类,则在该类的范围内查找嵌套名称说明符后指定的名称 ([class.member.lookup]),列出的情况除外以下。该名称应代表该类或其基类之一([class.衍生])的一个或多个成员。

...

上述名称查找规则的例外情况如下:

(1.1) 析构函数的查找在 [basic.lookup.qual] 中指定;

(1.2) conversion-function-id 的 conversion-type-id 的查找方式与类成员访问中的 conversion-type-id 相同(参见 [basic.lookup.classref]);

(1.3) 在整个后缀表达式出现的上下文中查找 template-id 的 template-argument 中的名称;

(1.4) 对 using 声明 ([namespace.udecl]) 中指定的名称的查找还会查找隐藏在同一范围内的类或枚举名称。

没有任何例外情况适用于 的情况A::B。没有命名析构函数,没有转换类型 ID,没有模板,也没有 using 声明。

A位于范围内,并且B是 的可访问成员A,因此查找应该成功。