Trivial and Standard-Layout Types in C++ Explained
In C++, low-level memory operations and inter-language compatibility rely on specific guarantees about object construction, destruction, and memory arrangement. This article provides a direct overview of trivial types, which can be safely copied using raw byte operations without custom lifecycle logic, and standard-layout types, which guarantee a predictable memory organization compatible with the C programming language. Understanding these two concepts clarifies how modern C++ categorizes data structures and replaces the legacy concept of Plain Old Data (POD).
What Is a Trivial Type?
A trivial type in C++ is a type whose fundamental lifecycle
operations—such as creation, copying, moving, and destruction—involve no
custom user logic and can be handled via direct memory operations like
std::memcpy.
A class, struct, or union is considered trivial if it meets the following criteria:
- Trivial default constructor: It has either an
implicitly generated default constructor or one explicitly defined with
= default. - Trivial copy and move operations: Its copy constructors, move constructors, copy assignment operators, and move assignment operators are all trivial or deleted.
- Trivial destructor: Its destructor is not user-defined and is not virtual.
- Trivial members and bases: All base classes and non-static data members must also be trivial types.
- No virtual features: It contains no virtual functions and inherits from no virtual base classes.
Scalar types (such as int, double,
pointers, and enumerations) and arrays of trivial types are inherently
trivial.
The primary benefit of a trivial type is performance. The compiler
can bypass constructor and destructor function calls entirely,
allocating and freeing memory instantaneously. You can query whether a
type satisfies this property at compile time using the type trait
std::is_trivial<T>::value (or
std::is_trivially_copyable<T>::value).
What Is a Standard-Layout Type?
A standard-layout type governs the physical arrangement of an object's members in memory. It guarantees that the memory alignment and member offsets are completely predictable, making the type binary-compatible with the C language.
Unlike triviality, standard-layout has nothing to do with whether constructors or destructors contain custom code. Instead, it enforces strict structural rules:
- Uniform access control: All non-static data members
must have the same access specifier (
public,protected, orprivate). Mixing access levels is not allowed because compilers are permitted to order members with different access levels arbitrarily. - No virtual features: It has no virtual functions and no virtual base classes.
- Member hierarchy rules: Either the most-derived class has non-static data members and no base class with non-static data members, or all non-static data members are located in a single base class.
- No identical base types as first member: The first non-static data member cannot be of the same type as any base class, preventing layout conflicts under the Empty Base Optimization (EBO).
- Standard-layout members and bases: All non-static data members and base classes must themselves be standard-layout types.
Standard-layout types allow developers to use the
offsetof macro safely to inspect member offsets and
guarantee safe data exchange with foreign function interfaces (FFIs),
such as C or hardware registers. This property can be checked using
std::is_standard_layout<T>::value.
Key Differences and Modern POD
While triviality and standard-layout often overlap, they address two orthogonal concerns:
| Feature | Trivial Type | Standard-Layout Type |
|---|---|---|
| Primary Focus | Object lifecycle (construction, copying, destruction) | Physical memory arrangement |
| Virtual Functions Allowed? | No | No |
| Custom Constructors Allowed? | No | Yes |
| Mixed Access Specifiers Allowed? | Yes | No |
Safe for
std::memcpy? |
Yes | Only if trivially copyable |
| Safe for C Compatibility? | Not guaranteed | Yes |
Historically, C++ defined a "Plain Old Data" (POD) type as a type
that was both trivial and standard-layout. However, C++20 formally
deprecated std::is_pod because bundling both concepts into
a single term caused unnecessary confusion. Modern C++ encourages
developers to query std::is_trivially_copyable when
optimizing copies or serialization, and
std::is_standard_layout when ensuring ABI and memory-layout
compatibility.