为什么为联合或类似联合的类删除了默认的默认构造函数?

struct A{
    A(){}
};
union C{
   A a;
   int b = 0;
};
int main(){
    C c;
}

在上面的代码中,GCC 和 Clang都抱怨联合的默认构造函数C被定义为已删除。

但是,相关规则说:

在以下情况下,类 X 的默认默认构造函数被定义为已删除:

  • X 是一个联合,它有一个带有非平凡默认构造函数的变体成员,并且 X 的任何变体成员都没有默认成员初始值设定项
  • X 是一个非联合类,它有一个变体成员 M 和一个非平凡的默认构造函数,并且包含 M 的匿名联合的变体成员没有一个默认成员初始值设定项

注意强调的措辞。在示例 IIUC 中,由于变体成员b具有默认成员初始值设定项,因此不应将默认的默认构造函数定义为已删除。为什么这些编译器会将此代码报告为格式错误?

如果将定义更改C

union C{
   A a{};
   int b;
};

然后所有的编译器都可以编译这段代码。该行为暗示该规则实际上意味着:

X 是一个联合,它有一个带有非平凡默认构造函数的变体成员,并且没有为变体成员提供默认成员初始值设定项

这是编译器错误还是该规则的措辞含糊不清?

回答

这在 C++14 和 C++17 之间通过CWG 2084进行了更改,它添加了允许(任何)联合成员上的 NSDMI 恢复默认的默认构造函数的语言。

CWG 2084 随附的示例与您的示例略有不同:

struct S {
  S();
};
union U {
  S s{};
} u;

这里 NSDMI 位于非平凡成员上,而 C++17 采用的措辞允许任何成员上的 NSDMI恢复默认的默认构造函数。这是因为,正如该 DR 中所写,

NSDMI 基本上是mem-initializer 的语法糖

也就是说,NSDMI onint b = 0;基本上相当于写一个带有 mem-initializer 和空体的构造函数:

C() : b{/*but use copy-initialization*/ 0} {}

顺便说一句,该规则确保在大多数工会的一个变体构件具有NSDMI有所隐藏在一个小节class.union.anon:

4 - [...] 最多一个联合的变体成员可以有一个默认的成员初始值设定项。

我的假设是,由于 gcc 和 Clang 已经允许上述(非平凡联合成员上的 NSDMI),他们没有意识到他们需要更改他们的实现以获得完整的 C++17 支持。

这在 2016 年的 std-discussion 列表中进行了讨论,示例与您的非常相似:

struct S {
    S();
};
union U {
    S s;
    int i = 1;
} u;

结论是,clang 和 gcc 在拒绝方面有缺陷,尽管当时有一个误导性的说明,因此进行了修改。

对于 Clang,错误是https://bugs.llvm.org/show_bug.cgi?id=39686,它使我们回到 SO 处,由于变体成员 N3690/N4140 与 N4659/N4727 删除了隐式定义的构造函数。我找不到 gcc 的相应错误。

请注意,MSVC 正确接受并初始化c.b = 0,这对于每个 dcl.init.aggr都是正确的:

5 - [...] 如果聚合是联合并且初始值设定项列表为空,则

  • 5.4 - 如果任何变体成员具有默认成员初始值设定项,则该成员从其默认成员初始值设定项进行初始化;[...]

以上是为什么为联合或类似联合的类删除了默认的默认构造函数?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>