Aggregate initialization is not value initialization
If a given class, say S
, is an aggregate, then
S s{};
is aggregate initialization, not value initialization, and will bypass any constructors of S
, even if they are deleted or private. The result of aggregate initialization is typically value-initialization of the (public(1)) data members of the aggregate.
(1) As shown below, a class with any private data member is never an aggregate class, not through any of the C++11 through C++20 standards.
The definition of what is an aggregate class is governed by:
where the typical confusion with aggregates is the weaker requirement of C++11 through C++17 to not have any user-provided(2) constructors, which was made much stricter in C++20 with the requirement to not have any user-declared constructors.
(2) The definition of what counts as user-provided function is governed by [dcl.fct.def.default]/4 in C++11 and [dcl.fct.def.default]/5 in C++14 through C++20; the definition of user-provided is essentially the same in all these language versions.
There are also other changes to the definition of aggregates between C++11, C++14 and C++17 that are not as well-known, but that likewise add to the aggregate confusion prior to C++20; we'll walk through a couple of examples to highlight them.
Is the example an aggregate?
For each example below; if the class S
of the example is an aggregate (for a particular language version), the following is well-formed:
S s{};
The aggregate rules in all the examples above likewise applies for any kind of constructor (given that they are otherwise applicable, e.g. allows to be explicitly defaulted or deleted), not just S()
.
struct S {};
- C++11: yes
- C++14: yes
- C++17: yes
- C++20: yes
struct S {
int x;
};
- C++11: yes
- C++14: yes
- C++17: yes
- C++20: yes
struct S {
int x{42};
};
- C++11: no (has a default member initializer)
- C++14: yes
- C++17: yes
- C++20: yes
class S {
int x;
};
- C++11: no
- C++14: no
- C++17: no
- C++20: no
Has a private data member.
struct S {
S() = default;
};
- C++11: yes
- C++14: yes
- C++17: yes
- C++20: no (has a user-declared constructor)
struct S {
private:
S() = default;
};
- C++11: yes
- C++14: yes
- C++17: yes
- C++20: no (has a user-declared constructor)
struct S {
S() = delete;
};
- C++11: yes
- C++14: yes
- C++17: yes
- C++20: no (has a user-declared constructor)
struct S {
S();
};
S::S() = default;
- C++11: no (has a user-provided constructor)
- C++14: no (...)
- C++17: no (...)
- C++20: no (has a user-declared constructor)
An out of line explicitly-defaulted definition counts as user-provided; recalling [dcl.fct.def.default]/4 (C++11) / [dcl.fct.def.default]/5 (C++14, C++17):
A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.
struct S {
explicit S() = default;
};
- C++11: yes
- C++14: yes
- C++17: no (has an
explicit
constructor)
- C++20: no (has a user-declared constructor)
C++17 adds the requirement that an aggregate may not have an explicit
constructor.
struct S {
int x{42};
S() = delete;
};
- C++11: no (has a default member initializer)
- C++14: yes
- C++17: yes
- C++20: no (has a user-declared constructor)
C++14 removed the requirement of C++11 for an aggregate to have no default member initializers.
struct S {
int x{42};
explicit S() = delete;
};
- C++11: no (has a default member initializer)
- C++14: yes
- C++17: no (has an
explicit
constructor)
- C++20: no (has a user-declared constructor)