聚合字段构造函数必须是公共的才能在 C++ 中使用聚合初始化吗?

Fed*_*dor 7 c++ language-lawyer aggregate-initialization c++20

请考虑带有带有私有构造函数B的类字段的聚合结构的代码A

class A { A(int){} friend struct B; };
struct B { A a{1}; };

int main()
{
    B b; //ok everywhere, not aggregate initialization
    //[[maybe_unused]] B x{1}; //error everywhere
    [[maybe_unused]] B y{}; //ok in GCC and Clang, error in MSVC
}
Run Code Online (Sandbox Code Playgroud)

我的问题是关于B. 由于初始化是代表调用代码(main此处的函数)进行的,我预计编译器必须拒绝它,因为A的构造函数是私有的。事实上B{1},所有编译器的构造都失败了。

但令我惊讶的B{}是,GCC 和 Clang 都接受了该构造,演示:https : //gcc.godbolt.org/z/7851esv6Y

并且只有 MSVC 拒绝它并显示错误 error C2248: 'A::A': cannot access private member declared in class 'A'

是 GCC 和 Clang 中的错误,还是标准允许他们接受此代码?

dfr*_*fri 3

\n

...具有聚合结构B...

\n
\n

为了完整起见,我们首先要注意,B根据[dcl.init.aggr]/1(N4861(2020 年 3 月布拉格后工作草案/C+) ,这确实是 C++14 到 C++20 中的聚合+20 DIS )):

\n
\n

聚合是一个数组或一个类 ([class])

\n
    \n
  • (1.1) 没有用户声明或继承的构造函数 ([class.ctor]),
  • \n
  • (1.2) 没有私有或受保护的直接非静态数据成员 ([class.access]),
  • \n
  • (1.3) 没有虚函数 ([class.virtual]),并且
  • \n
  • (1.4) 没有虚拟、私有或受保护的基类 ([class.mi])。
  • \n
\n
\n

而在 C++11 中,B由于违反了非静态数据成员的大括号或等于初始化器,因此被取消了作为聚合的资格,这一要求在 C++14 中已被删除。

\n

因此,根据[dcl.init.list]/3 B x{1}, 和B y{}都是聚合初始化:

\n
\n

对象或类型引用的列表初始化T定义如下:

\n
    \n
  • [...]
  • \n
  • (3.4) 否则,如果T是聚合,则执行聚合初始化 ([dcl.init.aggr])。
  • \n
\n
\n

对于前一种情况,的B x{1}数据成员是聚合的显式初始化元素,根据[dcl.init.aggr]/3。这意味着,根据[dcl.init.aggr]/4,特别是/4.2,数据成员是从初始化子句复制初始化的,这需要在聚合初始化的上下文中构造一个临时对象,使程序格式错误,因为 的匹配构造函数是私有的。aBAA

\n
B x{1}; // needs A::A(int) to create an A temporary\n        // that in turn will be used to copy-initialize\n        // the data member a of B.\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们A在初始化子句中使用对象,则无需A在聚合初始化上下文中访问私有构造函数,并且程序格式良好。

\n
class A { \n  public:\n    static A get() { return {42}; }\n  private:\n    A(int){}\n    friend struct B;\n};\n\nstruct B { A a{1}; };\n\nint main() {\n    auto a{A::get()};\n    [[maybe_unused]] B x{a}; // OK\n}\n
Run Code Online (Sandbox Code Playgroud)\n

对于后一种情况,B y{},根据[dcl.init.aggr]/3.3, 的数据成员a不再B是聚合的显式初始化元素,并且根据[dcl.init.aggr]/5,特别是/5.1

\n
\n

对于非联合聚合,每个不是显式初始化元素的元素都按如下方式初始化:

\n
    \n
  • (5.1) 如果元素具有默认成员初始值设定项 ([class.mem]),则从该初始值设定项初始化该元素。
  • \n
  • [...]
  • \n
\n
\n

a并且的数据成员B是从其默认成员初始值设定项初始化的,这意味着私有构造函数A::A(int)不再从不可访问的上下文中访问。

\n
\n

最后是私有析构函数的情况

\n
\n

如果我们添加私有析构函数,A那么所有编译器都会用正确的错误来演示它:

\n
\n

由[dcl.init.aggr]/8 [重点是我的]管理:

\n
\n

类类型的每个元素的析构函数都可能从发生聚合初始化的上下文中调用([class.dtor]) 。[ 注意:此规定确保在抛出异常的情况下可以为完全构造的子对象调用析构函数([except.ctor])。\xe2\x80\x94 尾注]

\n
\n