Aggregate Extensions

Using Extended Aggregate Initialization

However, there are additional constraints for aggregates to be able to initialize them:

Backward Incompatibilities

struct Derived;

struct Base {
friend struct Derived;
private:
  Base() {
  }
};

struct Derived : Base {
};

int main()
{
  Derived d1{};    // ERROR since C++17
  Derived d2;      // still OK (but might not initialize)
}

Before C++17

Derived was not an aggregate since it is derived from Base. Thus, Derived d1{}; called the implicitly defined default constructor of Derived, which by default called the default constructor of the base class Base. Although the default constructor of the base class is private, calling it via the default constructor of the derived class was valid because the derived class was defined to be a friend class.

Since C++17

Derived in this example is an aggregate, with no implicit default constructor at all (the constructor is not inherited by a using declaration). Therefore, the initialization is an aggregate initialization, which means that the expression std::is_aggregate<Derived>::value yields true. However, you cannot use brace initialization because the base class has a private constructor (see the previous section). Whether the base class is a friend is irrelevant.

cond0=>condition: brace ini?
cond=>condition: Is it aggregate?
op=>operation: ctor ini
cond2=>condition: private or protected base class members?
private or protected constructors?
op2=>operation: aggregate ini
err=>end: Error
e=>end: End
cond0(yes)->cond
cond0(no)->op
cond(yes)->cond2
cond(no)->op
cond2(yes)->err
cond2(no)->op2

Comarison between Initialization

| Initialization typs | Syntax form | Zero-initializes built-ins | Calls constructor | Allows narrowing | Supports aggregates | Typical pitfalls | | ————————– | ——————————- | —————————————- | —————————- | —————- | ——————- | ———————————— | | Default initialization | T x; | ❌ (indeterminate for fundamental types) | ✅
(default constructor) | N/A | ❌ | Uninitialized built-ins | | Value initialization | T x{};
T x = T(); | ✅ | ✅
(default constructor) | ❌ | ✅ | Sometimes confused with default init | | Direct initialization | T x(args); | ❌ | ✅(constructor) | ✅ | ❌ | Most-vexing parse | | Copy initialization
Constructor-base only | T x = expr; | ❌ | ✅ (copy/move/elided) | ✅ | ❌ | Extra conversions allowed | | List initialization | T x{args};
T x = {args}; | Depends on args | ✅ (constructor) | ❌ | ✅ | std::initializer_list preference |