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

Compilation error when using empty list initialization constructor in C++17

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

是否有人知道以下代码在调用 B{} 而不是 B() 时在 C++14 中编译而不在 C++17 中编译的原因? (我在 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
}

在 C++14 中,definition of aggregate 是:

An aggregate is an array or a class (Clause [class]) with no user-provided constructors ([class.ctor]), no private or protected non-static data members (Clause [class.access]), no base classes (Clause [class.derived]), and no virtual functions ([class.virtual]).

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

然而,在 C++17 中,definition of aggregate 被更改为:

An aggregate is an array or a class with

  • no user-provided, explicit, or inherited constructors ([class.ctor]),
  • no private or protected non-static data members (Clause [class.access]),
  • no virtual functions, and
  • no virtual, private, or protected base classes ([class.mi]).

[ Note: Aggregate initialization does not allow accessing protected and private base class' members or constructors.  — end note ]

限制不再针对 any base classes,而是针对 virtual/private/protected 的。但是 B 有一个 public 基础 class。现在是聚合体!并且 C++17 聚合初始化确实允许初始化基础 class 子对象。

特别是,B{} 是聚合初始化,我们只是不为任何子对象提供初始化程序。但是第一个(也是唯一的)子对象是一个 A,我们试图从 {} 初始化它(在聚合初始化期间,任何没有显式初始化器的子对象都是从 {} 复制初始化的),我们不能这样做,因为 A 的构造函数受到保护,我们不是朋友(另请参阅引用的注释)。


请注意,为了好玩,在 C++20 中 definition of aggregate 将再次更改。

根据我的理解https://en.cppreference.com/w/cpp/language/value_initialization

B{} 执行 aggregate_initialization,

自 C++17 起:

The effects of aggregate initialization are:

  • Each direct public base, (since C++17) [..] is copy-initialized from the corresponding clause of the initializer list.

在我们的案例中:

If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default initializers, if provided in the class definition, and otherwise (since C++14) by empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.

所以B{/*constructor of A*/}需要构造基class A,它是受保护的...

C++17 n4659 的最终草案有一个兼容性部分,其中包含相对于以前版本的更改。

C.4.4 Clause 11: declarators [diff.cpp14.decl]

11.6.1
Change: Definition of an aggregate is extended to apply to user-defined types with base classes.
Rationale: To increase convenience of aggregate initialization.
Effect on original feature: Valid C++ 2014 code may fail to compile or produce different results in this International Standard; initialization from an empty initializer list will perform aggregate initialization instead of invoking a default constructor for the affected types:

struct derived;
struct base {
friend struct derived;
private:
base();
};
struct derived : base {};
derived d1{}; // Error. The code was well-formed before.
derived d2; // still OK

我用 -std=c++14 编译了上面的示例代码,它编译了但是用 -std=c++17 编译失败了。

我相信这可能是 OP 中的代码以 B{} 失败但以 B() 成功的原因。