使用非静态数据成员和嵌套类构造函数的类内初始化时出错

时间:2013-07-02 16:04:00

标签: c++ c++11 language-lawyer

以下代码非常简单,我希望它能编译好。

struct A
{
    struct B
    {
        int i = 0;
    };

    B b;

    A(const B& _b = B())
        : b(_b)
    {}
};

我用g ++版本4.7.2,4.8.1,clang ++ 3.2和3.3测试了这段代码。除了g ++ 4.7.2对此代码(http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770)的段错误之外,其他经过测试的编译器会给出错误消息,而这些消息并不能解释太多。

g ++ 4.8.1:

test.cpp: In constructor ‘constexpr A::B::B()’:
test.cpp:3:12: error: constructor required before non-static data member for ‘A::B::i’ has been parsed
     struct B
            ^
test.cpp: At global scope:
test.cpp:11:23: note: synthesized method ‘constexpr A::B::B()’ first required here 
     A(const B& _b = B())
                       ^

clang ++ 3.2和3.3:

test.cpp:11:21: error: defaulted default constructor of 'B' cannot be used by non-static data member initializer which appears before end of class definition
    A(const B& _b = B())
                    ^

使这些代码可编辑是可能的,似乎它应该没有区别。有两种选择:

struct B
{
    int i = 0;
    B(){} // using B()=default; works only for clang++
};

struct B
{
    int i;
    B() : i(0) {} // classic c++98 initialization
};

这段代码真的不正确或编译器错了吗?

2 个答案:

答案 0 :(得分:80)

  

这段代码真的不正确或编译器错了吗?

嗯,两个都没有。该标准存在缺陷 - 它表示在解析A的初始值设定项时B::i被认为已完成,而B::B()(使用B::i的初始值设定项)可以在A的定义中使用。这显然是循环的。考虑一下:

struct A {
  struct B {
    int i = (A(), 0);
  };
  A() noexcept(!noexcept(B()));
};

这有一个矛盾:B::B()隐含noexcept iff A()没有抛出,A()不抛出iff B::B() 没有 noexcept。这个领域还有许多其他周期和矛盾。

核心问题13601397会对此进行跟踪。特别注意核心问题1397中的这个注释:

  

解决这个问题的最好方法可能是使非静态数据成员初始化程序使用其类的默认构造函数构造错误。

这是我在Clang中为解决此问题而实施的规则的一个特例。 Clang的规则是在解析该类的非静态数据成员初始值设定项之前,不能使用类的默认默认构造函数。因此,Clang在这里发布诊断:

    A(const B& _b = B())
                    ^

...因为Clang在解析默认初始化器之前解析默认参数,并且此默认参数需要B的默认初始化器已经被解析(为了隐式定义B::B())。

答案 1 :(得分:0)

也许这就是问题:

  

§12.15。默认构造函数是默认构造函数,默认构造函数是默认构造函数,默认情况下是默认构造函数。   用(3.2)创建类类型的对象(1.8),或者在第一次声明后显式默认为

因此,首次查找时会生成默认构造函数,但查找将失败,因为A未完全定义,因此无法找到A中的B。