为什么为联合或类似联合的类删除了默认的默认构造函数?
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 - 如果任何变体成员具有默认成员初始值设定项,则该成员从其默认成员初始值设定项进行初始化;[...]