在C++ 17中使用空列表初始化构造函数时的编译错误

RyA*_*azi 21 c++ list-initialization c++17

我试图转向C++ 17时遇到了一个奇怪的问题.问题是在C++ 17中有些东西(我不确定是什么)在默认构造函数的情况下使列表初始化的工作方式不同.我试图搜索https://en.cppreference.com/w/cpp/language/list_initialization以获取更多信息,但我没有找到任何看起来相关的信息.

是否有人知道下面的代码在C++ 14中编译但在C++ 17中编译B{}而不是B()?(我在gcc 8.2和7.3以及icc 19中尝试过它)

struct A{
protected:
    A() {}
};

struct B : public A {};


B f(){
    return B(); //compilation OK
    //return B{}; //compilation error
}
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 27

在C++ 14中,聚合定义是:

聚合是一个数组或类(Clause [class]),没有用户提供的构造函数([class.ctor]),没有私有或受保护的非静态数据成员(Clause [class.access]),没有基类(子句[class.derived]),没有虚函数([class.virtual]).

因此,B不是聚合.因此B{}肯定不会聚合初始化,B{}B()最终意味着同样的事情.他们都只是调用B默认构造函数.

但是,在C++ 17中,聚合定义更改为:

聚集体是一个数组或具有类

  • 没有用户提供的,显式的或继承的构造函数([class.ctor]),
  • 没有私有或受保护的非静态数据成员(Clause [class.access]),
  • 没有虚拟功能,和
  • 没有虚拟,私有或受保护的基类([class.mi]).

[注意:聚合初始化不允许访问受保护和私有基类的成员或构造函数.   - 结束说明]

限制不再适用于任何基类,而只适用于虚拟/私有/受保护的基类.但是B有一个公共基础班.它现在是一个聚合!而C++ 17聚合初始化确实允许初始化基类子对象.

特别B{}是聚合初始化,我们只是不为任何子对象提供初始化器.但是第一个(也是唯一的)子对象是一个A,我们试图初始化{}(在聚合初始化期间,没有显式初始化器的任何子对象都是从中复制初始化的{}),这是我们无法做到的,因为它A的构造函数受到保护,我们不是朋友(另见引用说明).


请注意,只是为了好玩,在C++ 20中,聚合定义将再次发生变化.