首页 > 解决方案 > 具有前向声明的嵌套类在 clang++ 中导致错误,但在 g++ 上通过时没有警告

问题描述

我正在尝试结合嵌套类和前向声明以保持代码清晰易读,即使它具有非常复杂的类结构。前向声明允许我在这里减少缩进级别。

以下代码在 g++-9.3 和 clang++-10 上都能正常编译:

class A {
    public:
        class B;
};
class A::B {
    public:
        int foo=0;
};

然而,当我在另一个类中做同样的嵌套时,这个构造在 g++ 上没有任何警告,但在 clang++ 上失败:

class Outer {
   public:
      class A {
         public:
            class B;
      };
      class A::B {
         public:
            int foo=0;
      };
};

clang++ 的失败是:

test.cpp:7:16: error: non-friend class member 'B' cannot have a qualified name
      class A::B {
            ~~~^
1 error generated.

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

标签: c++g++language-lawyerinner-classesclang++

解决方案


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

来自[class]/11

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

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

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

没有实现。特别要注意,类说明符包含class-head,而 class-head 又包含一个class-head-name,而 class-head-name 又包含一个可选的嵌套名称说明符,以及(非可选的)一个类-名字。如果存在嵌套名称说明符,则 [class]/11 适用,在这种情况下(如上文所强调的),适用于允许类说明符的位置;特别是,不允许嵌套在一个类中。

因此,您的程序格式错误;叮当是对的。


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

class Outer {
 public:
    class A {
      public:
        class B;
    };
};

class Outer::A::B {
  public:
    int foo=0;
};

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

如果 classX在命名空间范围内定义,则嵌套类Y可以在 class 中声明,然后在 classX的定义中定义, X或者稍后在包含 class 定义的命名空间范围内定义X。[_ 例子:_

 class E {   class I1;       // forward declaration of nested class  
 class I2;   class I1 { };   // definition of nested class }; class
 E::I2 { };  // definition of nested class

 <em>—结束示例]

这允许您的第一个示例:

class A {
  public:
    class B;
};
class A::B {
  public:
    int foo=0;
};

被重构为

class A {
  public:
    class B {
      public:
        int foo=0;
    };
};

或者

class A {
  public:
    class B;  // forward declaration
    // ...
    class B {
      public:
        int foo=0;
    };
};

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


推荐阅读