C++20显式默认/删除构造函数致聚合初始化失效原因及背景问询
问题解答
没错,你遇到的这个编译错误完全是C++20对聚合类型定义的修改导致的,咱们一步步拆解来看:
核心原因
先明确两个标准下聚合类型的关键差异:
- C++17:允许聚合类型包含显式默认/删除的构造函数,所以你的
struct Foo在C++17中属于聚合类型,Foo { 0 }会触发聚合初始化,直接给成员bar赋值,完全合法。 - C++20:聚合类型的定义被收紧——任何带有用户声明构造函数(哪怕是
= default或= delete的)的类型都不再是聚合类型。这时候你的Foo因为声明了Foo() = default;,已经不是聚合了,Foo { 0 }会尝试调用构造函数,但Foo并没有接受int的构造函数,编译器就会报构造函数重载解析歧义的错误(本质是找不到匹配的构造函数)。
设计考量
标准委员会修改这个规则,核心是为了消除语义模糊:
在C++17的规则下,有些类型的身份很尴尬——既有用户声明的构造函数,又是聚合类型,这会让开发者产生困惑:我写的显式默认构造函数到底会不会影响聚合初始化的行为?比如如果后续给类加了其他构造函数,会不会突然改变初始化的逻辑?
C++20把聚合类型的定义严格限定为「没有任何用户声明构造函数」的类型,让聚合的语义变得非常清晰:只要你写了任何构造函数(不管是自定义的还是默认/删除的),这个类型就不再是聚合,初始化行为完全遵循构造函数重载规则;反之,没有任何用户声明构造函数的类型,才会触发聚合初始化。
兼容性与变更意图
这个变更确实是故意的,但标准委员会并非为了破坏兼容性而修改,而是权衡了「语义清晰性」和「兼容性破坏范围」后的决定:
- 受影响的代码通常是那些依赖「带显式默认构造函数的聚合类型」的场景,这类代码的数量相对有限。
- 而且修复成本很低,开发者可以通过简单调整代码来适配新规则(见下文修复方案)。
委员会认为,消除语义模糊带来的长期收益,远大于短期的兼容性成本。
背后的收益
这个规则变更带来的核心收益有三点:
- 语义一致性:聚合类型的判定规则变得直观易懂,开发者不需要再记忆复杂的例外情况,一眼就能判断一个类型是否是聚合。
- 避免意外行为:之前C17中,有些场景下聚合初始化和构造函数调用的边界很模糊,容易导致开发者写出不符合预期的代码,C20的规则彻底消除了这种模糊。
- 简化标准与实现:收紧后的规则让标准文档的描述更简洁,编译器的实现逻辑也更清晰,减少了潜在的编译器bug。
修复方案
针对你的代码,有两种简单的修复方式:
- 保持聚合类型:如果不需要显式声明默认构造函数,直接删掉
Foo() = default;——编译器会自动生成默认构造函数,Foo在C++20中依然是聚合类型,Foo { 0 }可以正常编译。 - 添加匹配的构造函数:如果确实需要显式默认构造函数(比如要抑制编译器生成的其他特殊成员函数),可以添加一个接受
int的构造函数:struct Foo { Foo() = default; explicit Foo(int b) : bar(b) {} // 新增构造函数 int bar; }; auto test = Foo { 0 }; // 现在会调用这个构造函数,正常编译
内容的提问来源于stack exchange,提问作者sebrockm
相关产品推荐
相关产品推荐

