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


90

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

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

这段代码是真的不正确还是编译器错误?


3
我的G ++ 4.7.3 internal compiler error: Segmentation fault对此代码说...
Fred Foo

2
(错误C2864:“ A :: B :: i”:只能在类中初始化静态const整数数据成员)是VC2010所说的。该输出与g ++一致。Clang也这么说,尽管意义不大。int i = 0除非是,否则您不能通过这样做来默认结构中的变量static const int i = 0
克里斯·库珀

@Borgleader:顺便说一句,我避免了将表达式B()视为对构造函数的函数调用的诱惑。您永远不会直接“调用”构造函数。可以认为这是一种特殊的语法,它会创建一个临时的B...,而构造函数只是该过程的一部分被调用,深层次地体现在随后的机制中。
Lightness Races in Orbit

2
嗯,向中添加构造函数B似乎可以在中使此工作gcc 4.7
Shafik Yaghmour

7
有趣的是,将A的构造函数的定义移出A似乎也可以使其工作(g ++ 4.7);带有“默认默认构造函数的钟声不能在类定义结束之前使用”。
moonshadow

Answers:


84

这段代码是真的不正确还是编译器错误?

好吧,两个都没有。该标准有一个缺陷-它A认为在解析初始化器时被认为是完整的B::i,并且可以在的定义中B::B()使用(使用初始化器的B::i)缺陷A。这显然是循环的。考虑一下:

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

这有一个矛盾:B::B()隐式地noexceptA()没有抛出,而A()不会引发当且仅当B::B()没有 noexcept。这个领域还有许多其他周期和矛盾。

这是由核心问题跟踪的 13601397。在核心问题1397中特别注意此说明:

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

这是我用Clang实施以解决该问题的规则的特例。Clang的规则是,在解析该类的非静态数据成员初始化器之前,不能使用该类的默认默认构造函数。因此Clang在这里发出诊断信息:

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

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


很高兴知道。但是错误消息仍然令人误解,因为构造函数实际上并未“由非静态数据成员初始化程序使用”。
aschepler

您是由于过去在此问题上的特定经验,还是只是仔细阅读标准(和缺陷列表)而知道吗?另外,+ 1。
Cornstalks

+1以获得此详细答案。那么,出路是什么呢?某种“两阶段类解析”,其中依赖于内部类的外部类成员的解析被延迟到内部类完全形成之前?
TemplateRex

4
@aschepler是的,这里的诊断不是很好。我已经为此提交了llvm.org/PR16550。
理查德·史密斯

@Cornstalks我在Clang中为非静态数据成员实现初始化程序时发现了此问题。
理查德·史密斯

0

也许这是问题所在:

§12.15.当默认使用(3.2)创建其类类型的对象(1.8)或在其首次声明后明确将其默认设置时,会隐式定义默认且未定义为删除的默认构造函数

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


我不确定“因此”。显然B b这不是问题,并且在其中找到显式方法/显式声明的构造函数B也不是问题。因此,很高兴看到一些定义,说明为什么在这里查找应该以不同的方式进行,以便在这种情况下我们可以宣布代码为非法之前,在这种情况下“找不到B内部A”,而在其他情况下则没有。
moonshadow

我在标准中发现在类初始化期间(包括在嵌套类内)类定义被认为是完整的。我没有费心去记录参考文献,因为它似乎并不相关。
Mark B

@moonshadow:声明说,使用odr时会隐式默认构造函数。在第一次声明后明确定义。B b不调用构造函数,A的构造函数调用B的构造函数
fscan

如果出现类似问题,如果您=0从中删除代码,代码仍然无效i = 0;。但是如果没有这样的话=0,代码是有效的,您将找不到一个抱怨B()在定义中使用的编译器A
aschepler
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.