In this post, I like to give you some details about the new definition in C++20 of a:
- 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
We can copy a trivial type into a
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:
virtualbase classes A;
- 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
protectedmembers, and it is still standard-layout;
- Has a standard-layout base class or classes E;
- All non-
staticdata members are standard-layout as well F;
- Meets one of these conditions:
- no non-
staticdata member in the most-derived class and no more than one base class with non-
staticdata members, or
- has no base classes with non-
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
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
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
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.
|Type||memcopy-able||C compatible memory layout|
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.