【问题标题】:Error when using in-class initialization of non-static data member and nested class constructor使用非静态数据成员和嵌套类构造函数的类内初始化时出错
【发布时间】:2013-06-30 02:28:51
【问题描述】:

以下代码非常简单,我希望它应该可以正常编译。

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

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

【问题讨论】:

  • 我的 G++ 4.7.3 对这段代码说 internal compiler error: Segmentation fault...
  • (错误 C2864: 'A::B::i' : 只有静态 const 整数数据成员可以在类中初始化)是 VC2010 所说的。该输出与 g++ 一致。 Clang 也这么说,尽管它的意义要小得多。您不能通过 int i = 0 来默认结构中的变量,除非它是 static const int i = 0
  • @Borgleader:顺便说一句,我会避免将表达式 B() 视为对构造函数的函数调用。您从不直接“调用”构造函数。将其视为创建临时 B... 的特殊语法,并且构造函数仅作为该过程的一部分被调用,深入随后的机制。
  • 嗯,向B 添加一个构造函数似乎可以在gcc 4.7 中进行这项工作。
  • 有趣的是,将 A 的构造函数的定义移出 A 似乎也使它工作(g++ 4.7);与“不能使用默认的默认构造函数......在类定义结束之前”的提示。

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


【解决方案1】:

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

嗯,也不是。该标准有一个缺陷——它说A在解析B::i的初始化程序时被认为是完整的,并且B::B()(它使用B::i的初始化程序)可以在@987654327的定义中使用@。这显然是循环的。考虑一下:

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。
  • +1 获取详细答案。那么出路会是什么?某种“两阶段类解析”,其中依赖于内部类的外部类成员的解析被延迟到内部类完全形成?
  • @aschepler 是的,这里的诊断不是很好。我已经为此提交了 llvm.org/PR16550。
  • @Cornstalks 我在 Clang 中为非静态数据成员实现初始化程序时发现了这个问题。
【解决方案2】:

也许这就是问题所在:

§12.1 5. 一个默认的且未定义为已删除的默认构造函数在它是 odr- 时被隐式定义 用于 (3.2) 创建其类类型 (1.8) 的对象或在其第一次声明后显式默认时

所以,第一次查找时会生成默认构造函数,但是查找会失败,因为 A 没有完全定义,因此 A 中的 B 将找不到。

【讨论】:

  • 我不确定“因此”。显然B b 不是问题,在B 中找到显式方法/显式声明的构造函数也不是问题。因此,很高兴看到一些关于为什么查找应该在此处以不同方式进行的定义,以便“B inside A is not found”在这种情况下而不是其他情况下,然后我们可以声明代码为此非法原因。
  • 我在标准中发现类定义在类内初始化期间被认为是完整的,包括在嵌套类中。我没有费心记录参考,因为它似乎不相关。
  • @moonshadow:该语句说隐式默认构造函数是在使用 odr- 时定义的。在第一次声明之后明确定义。而B b不调用构造函数,A的构造函数调用B的构造函数
  • 如果有这样的问题,如果您从i = 0; 中删除=0,代码仍然无效。但是没有=0,代码是有效的,你不会找到一个编译器抱怨在A的定义中使用B()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多