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

下面的代码非常简单,我希望它能够很好地编译。

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测试了这段代码。除了这段代码(http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770)上的 g + + 4.7.2 Segfault 这个事实之外,其他测试过的编译器提供的错误消息解释得不多。

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
};

这个代码是真的不正确还是编译器错了?

18912 次浏览

Maybe this is the problem:

§12.1 5. A default constructor that is defaulted and not defined as deleted is implicitly defined when it is odr- used (3.2) to create an object of its class type (1.8) or when it is explicitly defaulted after its first declaration

So, the default constructor is generated when first looked up, but the lookup will fail because A is not completely defined and B inside A will therefore not be found.

Is this code really incorrect or are the compilers wrong?

Well, neither. The standard has a defect -- it says both that A is considered complete while parsing the initializer for B::i, and that B::B() (which uses the initializer for B::i) can be used within the definition of A. That's clearly cyclic. Consider this:

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

This has a contradiction: B::B() is implicitly noexcept iff A() does not throw, and A() does not throw iff B::B() is not noexcept. There are a number of other cycles and contradictions in this area.

This is tracked by core issues 1360 and 1397. Note in particular this note in core issue 1397:

Perhaps the best way of addressing this would be to make it ill-formed for a non-static data member initializer to use a defaulted constructor of its class.

That's a special case of the rule that I implemented in Clang to resolve this issue. Clang's rule is that a defaulted default constructor for a class cannot be used before the non-static data member initializers for that class are parsed. Hence Clang issues a diagnostic here:

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

... because Clang parses default arguments before it parses default initializers, and this default argument would require B's default initializers to have already been parsed (in order to implicitly define B::B()).