C++20: Aggregate, POD, trivial type, standard layout class, what is what
In this post, I like to give you some details about the new definition in C++20 of a:
- Aggregate
- POD
- Trivial type
- Standard layout class
You may already know some of these terms, others may be new, and for some, the definition has changed.
A trivial type
In short, a trivial type is a class or struct for which the compiler provides all the special members, either implicitly or because they are explicitly defaulted by us. Once we provide our own default constructor for a class, such a type is no longer a trivial type. Another property of a trivial type is that such a type occupies a contiguous memory area, which makes it memcopy-able, and it supports static
initialization.
We can copy a trivial type into a char
or unsigned char
array and back. Alignment and also the size of the type may be different from char
due to alignment and padding rules.
One important property of trivial type is that they can have mixed access specifiers. We all know the rule that classes and structs are have the same layout order in memory as the order of the defined data members. However, whenever we have different access specifiers in a class, the standard specifies that, in this case, the order is unspecified.
1 2 3 4 5 6 7 8 9 10 |
|
This allows a compiler, at least in theory, because I don't know one compiler which takes the opportunity of this, to reorder the data members, of a struct. One way would be that the struct starts with all public
members followed by all private
data members:
1 2 3 4 5 6 7 8 |
|
As shown in A, this reordering causes an incompatibility with C, which doesn't know about private
and hence that reordering rule. The result is that a trivial type is not usable in C code.
When you have read the above carefully, you noticed that I talked only about special members, which we must either default or let the compiler provide. However, a trivial type can still have a user-provided constructor, as long as this is not the default constructor.
1 2 3 4 5 6 7 8 9 10 |
|
A standard-layout class
The term layout refers to the arrangement of members of classes, structs, or unions in memory. A standard-layout class defines a type, which does not use certain specific C++ features that are not available in C. Such a type is memcopy-able, and its layout is defined in a way that the same type can be used in a C program. So in more general speak, a standard-layout class (or type) is compatible with C and can be exchanged over a C-API.
With all that said, we have a standard-layout class if it does not contain any language elements which are not present in C. Here is a more complete definition. The numbers refer to the code example that follows:
- No
virtual
base classes A; - No
virtual
functions B; - No reference-members C;
- The same access control for all non-static data members D. Right this is different from the definition of a trivial type, we can have a
struct
with onlyprivate
orprotected
members, and it is still standard-layout; - Has a standard-layout base class or classes E;
- All non-
static
data members are standard-layout as well F; - Meets one of these conditions:
- no non-
static
data member in the most-derived class and no more than one base class with non-static
data members, or - has no base classes with non-
static
data members
Please note that at this point, we talk about the layout in the memory and the interoperability with C. What you do not see in the definition above is that a standard-layout class could have special members. They do not change the memory layout. Special members only help to initialize an object. Despite that C has no special members, we can have them in C++ in a standard-layout type because it is just about the layout, nothing else.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
POD
Before C++20, we had the definition of a POD type. The specification was that a POD is a type that is trivial and standard-layout. With C++20, the definition of POD, as well as the type-trait std::is_pod
, is gone. No worries, your favorite STL vendor will for sure provide the type-trait for some time before it actually gets removed.
The idea of a POD was that it supports two distinct properties:
- we can compile a POD in C++ and still use it in a C program, as it has the same memory layout in both languages (meet by standard-layout);
- a POD supports static initialization (meet by trivial type).
While a standard-layout type has a C compatible memory layout, it can have a user-defined default constructor. This is something C doesn't have. Hence we need the second property, a trivial type. As we learned above, such a type is default constructible the same way as a C struct.
As far as the C++20 standard is concerned, the term POD no longer exists. POD is replaced by standard-layout and trivial type. As a consequence, the type-trait std::is_pod
is deprecated in C++20, and you are encouraged to use the two type-traits std::is_trivial
and std::is_standard_layout
.
Aggregate
An aggregate can be seen as a composition of other types. All data members of an aggregate must be public
. The interesting thing is that since C++17 aggregates can have public
base classes as long as they are not virtual
. These base classes do not need to be aggregates themself. If they are not, they are list-initialized.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Above, we see such an aggregate. SomeType
derives public from Base
, which is not an aggregate. Due to the constructor, we provided we lost the default constructor, which makes Base
no longer an aggregate. In SomeType
, we have another non-aggregate example, the member y
of type A
. The rules from Base
apply to A
as well. We provided a constructor hence we lost the default constructor. In D, we see that we still can initialize all members and base classes of SomeType
.
An aggregate can be standard-layout and trivial. However, as an aggregate can contain references, it is not always standard-layout or a trivial type.
Another interesting property of an aggregate is that aggregates are always decomposable in a structured binding.
Properties Overview
Type | memcopy-able | C compatible memory layout |
---|---|---|
Trivial type | Yes | No |
Standard-layout | Yes | Yes |
Aggregate | Yes | maybe |
Why should you care?
Why do you need to differentiate? One apparent reason is compatibility with C. Should that be the purpose, you must ensure that the type in question is trivial and standard-layout. For data exchange via network or file, you can use a type that satisfies only trivial or standard-layout. I would recommend either aiming for both or at least standard-layout. Because with only trivial, you risk that future compiler's or a different compiler on the other side, do rearrange data members, and then a receiver may have a different layout understanding than the sender.
Andreas