1. Changelog
- 
     R0 (May 2024): - 
       Initial revision. See relevant minutes from Kona 2022, Issaquah 2023. 
 
- 
       
2. Three intuitions for TC
Different audiences think about trivial copyability (TC) in different ways. Here are three points of view I’ve identified: the library-writer, the compiler-writer, and the abstract-machine-lawyer.
2.1. Library-writer’s intuition
The library-writer’s view is also shared by the trainer/instructor; it’s the view that you need to have in order to understand most C++ codebases these days.
When a type is trivially copyable, all of its Rule-of-Five operations can be lowered to simple
s. Furthermore, its Rule-of-Five operations are non-throwing and have no visible side effects.memcpy 
This is the intuition by which every STL vendor writes things like this,
which assumes that assignment of TC types can always be lowered to 
template < class T , class U > U * std_copy ( T * first , T * last , U * dfirst ) { static_assert ( is_assignable_v < T & , U &> ); if constexpr ( same_as < T , U > && is_trivially_copyable_v < T > ) { memmove ( dfirst , first , ( last - first ) * sizeof ( T )); return dfirst + ( last - first ); } else { while ( first != last ) * dfirst ++ = * first ++ ; return dfirst ; } } 
And this, which assumes that assignment and construction can both be lowered:
template < class T > void std_vector < T >:: insert ( std_vector < T >:: const_iterator it , T value ) { size_t i = it - begin (); ensure_capacity (); if constexpr ( is_trivially_copyable_v < T > ) { memmove ( data () + i + 1 , data () + i , size_ - i ); size_ += 1 ; } else { :: new ( data ()[ size_ ]) T ( std :: move ( data ()[ size_ - 1 ])); size_ += 1 ; move ( data () + i , data () + size_ - 1 , data () + i + 1 ); } data ()[ i ] = value ; } 
A library might even assume that copying a TC type is "free" because it cannot have visible side effects:
template < class T , class Compare > void sort3 ( T * x , T * y , T * z , Compare & c ) { if constexpr ( is_trivially_copyable_v < T > && is_constructible_v < T , T &> && is_assignable_v < T & , T &> ) { // cond swap bool r = c ( * z , * y ); T tmp = r ? * z : * y ; * z = r ? * y : * z ; * y = tmp ; // partially sorted swap r = c ( * z , * x ); tmp = r ? * z : * x ; * z = r ? * x : * z ; r = c ( tmp , * y ); * x = r ? * x : * y ; * y = r ? * y : tmp ; } else { // use swap alone, which is slower if ( c ( * y , * x )) { if ( c ( * z , * y )) { swap ( * x , * z ); } else { swap ( * x , * y ); if ( c ( * z , * y )) { swap ( * y , * z ); } } } else if ( c ( * z , * y )) { swap ( * y , * z ); if ( c ( * y , * x )) { swap ( * x , * y ); } } } 
The library-writer’s intuition is the most fruitful, because it leads to all these cool optimizations. Unfortunately, we’ll see that each of these optimizations has corner cases today which are unsafe, because the set of types actually considered TC by C++ includes some types for which these optimizations are unsafe.
2.2. Compiler-writer’s intuition
The compiler-writer’s intuition is that all types start out "naturally" TC, and then get "made non-TC"
in various ways. For example, if we see that any of 
This paradigm is extremely easy to implement. It’s also powerful and general: the compiler-writer can use the same technique to track "trivial relocatability," "trivial equality-comparability," "trivial value-initializability," etc. Unfortunately, we’ll see that this paradigm doesn’t work perfectly without some tweaks, because the set of types actually considered TC by C++ includes some types violating this paradigm. (For example, a TC type can have a non-TC type as a data member.)
2.3. Abstract-machine-lawyer’s intuition
Our previous two intuitions were driven by the physics of TC: what library operations can we do efficiently for TC types, and how can we efficiently compute the TC-ness of a type inside the compiler. The abstract-machine-lawyer’s intuition is not driven by physics, but by the circular logic of the Standard itself. [basic.types.general]/2–3 says:
2. For any [complete object] of TC type
[...] the underlying bytes making up the object can be copied into an array of char[, and when] that array is copied back into the object, the object shall subsequently hold its original value.T 3. For two distinct [complete] objects
andobj1 of TC typeobj2 , if the underlying bytes making upT are copied intoobj1 ,obj2 shall subsequently hold the same value asobj2 .obj1 
This means that (by some mystical process) you can use 
struct Stone { int i ; Stone () {} Stone ( Stone && ) = default ; void operator = ( const Stone & ) = delete ; void operator = ( Stone && ) = delete ; ~ Stone () = default ; }; void via_assign ( Stone & a , Stone & b ) { a = b ; // ill-formed } void via_memcpy ( Stone & a , Stone & b ) { std :: memcpy ( & b , & a , sizeof ( Stone )); // OK, b now holds the same value as a, by [basic.types.general]/3 } 
Over the years, CWG participants have suggested different metaphysical explanations for how 
b . ~ Stone (); // trivial :: new ( & b ) Stone ( std :: move ( a )); // trivial 
Moving-from 
The abstract-machine-lawyer doesn’t care about whether that trivial move-constructor is
selectable by overload resolution, whether it’s ambiguous, or private, or anything else;
it suffices for the abstract machine that we have sufficient non-deleted Rule-of-Five members
for the abstract machine’s 
The minutes of CWG2463 discussion in Kona, November 2022 capture the abstract-machine-lawyer’s position perfectly: "A class should be TC if there is a way [i.e., any way] to trivially copy or move it."
Consider again our 
Now, if our TC type is not an implicit-lifetime type ([class.prop]/9),
i.e. it is TC but has no eligible trivial constructors, then the above logic doesn’t work;
but in that case we must have an eligible trivial copy- or move-assignment operator
(so 
Unfortunately, as we’ll see below, today it is possible to create a TC type that is copy-constructible
without giving it an eligible copy constructor. Such a TC type can be put into a 
2.4. Bottom line
There is a tension between the library-writer’s intuition and the abstract-machine-lawyer’s intuition.
The library-writer wants C++ to report types as "TC" only if their Rule-of-Five operations
can be safely lowered to 
The abstract-machine-lawyer wants C++ to report types as "non-TC" only if it would
never make sense to 
The library-writer wants a strictly conservative "TC" label, with no false positives, because each false positive means misbehavior at runtime. (We’ll see some false positives below.) The abstract-machine-lawyer tends to want a liberal "TC" label, with as few as possible false negatives, because each false negative means a program that works correctly in practice, but formally has UB.
3. Status quo
The Standard defines several kinds of "triviality." Let’s cover each of them quickly.
1. 
2. 
A trivially copyable class is a class:
that has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator,
where each eligible copy constructor, move constructor, copy assignment operator, and move assignment operator is trivial, and
that has a trivial, non-deleted destructor.
Scalar types, trivially copyable class types, arrays of such types, and cv-qualified versions of these types are collectively called trivially copyable types.
3. 
[is true whenever] the variable definitionis_trivially_constructible < T , Args ... > is well-formed and is known to call no operation that is not trivial. Access checking is performed as if in a context unrelated toT t ( declval < Args > ()...); and any of theT .Args [
is true whenever] the expressionis_trivially_assignable < T , U > is well-formed when treated as an unevaluated operand. and is known to call no operation that is not trivial. Access checking is performed as if in a context unrelated todeclval < T > () = declval < U > () andT .U 
Now, this wording depends on the definition of "trivial operation," which the Standard conspicuously fails to define.
In practice, compiler vendors treat every operation as trivial unless it is a function call, in which case it
is trivial if-and-only-if it calls a trivial special member function (as defined below).
Notably, 
struct Agg { int i ; }; static_assert ( std :: is_trivially_constructible_v < Agg , int > ); static_assert ( std :: is_trivially_constructible_v < Agg , float > ); 
Note: There is implementation divergence on lambda conversions, which are arguably "known" to the compiler
despite acting like user-defined conversion functions.
EDG+MSVC say that 
Note: There is implementation divergence on NSDMIs, which are arguably "known" to the compiler.
Given 
Given 
Note: There is likely-accidental implementation divergence on aggregate initialization from
multiple arguments.
Given 
5. A special member function is trivial in specific circumstances ([class.default.ctor]/3, [class.copy.ctor]/11, [class.copy.assign]/9, [class.dtor]/8).
A default constructor is trivial if it is not user-provided and if:
its class has no virtual functions and no virtual base classes, and
no non-static data member of its class has a default member initializer, and
all the direct base classes of its class have trivial default constructors, and
for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.
A copy/move constructor for class
is trivial if it is not user-provided and if:X 
class
has no virtual functions and no virtual base classes, andX 
the constructor selected to copy/move each direct base class subobject is trivial, and
for each non-static data member of
that is of class type (or array thereof), the constructor selected to copy/move that member is trivial.X A copy/move assignment operator for class
is trivial if it is not user-provided and if:X 
class
has no virtual functions and no virtual base classes, andX 
the assignment operator selected to copy/move each direct base class subobject is trivial, and
for each non-static data member of
that is of class type (or array thereof), the assignment operator selected to copy/move that member is trivial.X A destructor is trivial if it is not user-provided and if:
the destructor is not virtual,
all of the direct base classes of its class have trivial destructors, and
for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.
Note: [class.default.ctor] conspicuously doesn’t require that "the constructor selected to initialize that member is trivial"; it simply requires that each base and member have a (possibly ineligible) trivial default constructor. Naturally, there is implementation divergence here (Godbolt). EDG seems to follow the letter of the law; Clang+GCC+MSVC hew closer to the library-writer’s intuition.
Notice that 
Two more critical things to keep in mind for the following examples:
- 
     A copy constructor, copy assignment operator, etc, is never a template. So you can have a poorly-matching trivial copy constructor living alongside a non-trivial constructor template that is a better match; yet this will not cause is_trivially_copyable 
- 
     The rule for calling a special member function "trivial" is context-independent. Once trivial, always trivial. So if you inherit a member function from your base class, it will continue to "be trivial" for you, just like it was trivial for your base class. 
4. Surprising behaviors
4.1. TC types can have non-TC members
struct Hamlet { Hamlet ( const Hamlet & ) { puts ( "copied" ); } Hamlet ( Hamlet && ) = default ; Hamlet & operator = ( Hamlet && ) = default ; }; struct Nutshell { Hamlet h_ ; Nutshell ( Nutshell && ) = default ; Nutshell & operator = ( Nutshell && ) = default ; }; static_assert ( ! std :: is_trivially_copyable_v < Hamlet > ); static_assert ( std :: is_trivially_copyable_v < Nutshell > ); 
Note: There is implementation divergence. EDG+MSVC say 
To paraphrase Daveed Vandevoorde’s comment on that issue:
"It is unclear why a complete object of type 
But to the library-writer intuition, this example is perfectly sane: TC means "all provided operations are tantamount to 
Finally, this example is problematic for the compiler-writer intuition. We want to be able to track a single bit, "
struct Hamlet2 { Hamlet2 ( const Hamlet2 & ) { puts ( "copied" ); } Hamlet2 ( Hamlet2 && ) = default ; Hamlet2 & operator = ( Hamlet2 && ); // not defaulted }; struct Nutshell { Hamlet2 h_ ; Nutshell ( Nutshell && ) = default ; Nutshell & operator = ( Nutshell && ) = default ; }; static_assert ( ! std :: is_trivially_copyable_v < Hamlet2 > ); static_assert ( ! std :: is_trivially_copyable_v < Nutshell > ); 
Non-TC 
4.2. Types can act non-trivially while publicly advertising TC
Microsoft STL’s 
struct Plum { int * ptr_ ; explicit Plum ( int & i ) : ptr_ ( & i ) {} Plum ( const Plum & ) = default ; void operator = ( const volatile Plum & ) = delete ; template < class = void > void operator = ( const Plum & rhs ) { * ptr_ = * rhs . ptr_ ; } }; static_assert ( std :: is_trivially_copyable_v < Plum > ); static_assert ( std :: is_assignable_v < Plum & , const Plum &> ); static_assert ( ! std :: is_trivially_assignable_v < Plum & , const Plum &> ); 
Microsoft STL’s 
This example isn’t problematic for the compiler-writer nor the abstract-machine-lawyer. But it is shocking to everyone’s intuition because it shows that we can easily use corner cases in the wording to decouple the "advertised" TC-ness of a type from its "proper" semantic TC-ness. (See [Müller] and [ODwyer].)
Show this example to the library-writer and when they recover from the shock they’ll say:
"Okay, so we shouldn’t gate our 
4.3. "Trivial" Rule-of-Five functions can act unlike memcpy
The rule for calling a special member function "trivial" lacks context. Once trivial, always trivial.
So if you inherit a member function from your base class, it will continue to "be trivial" for you,
just like it was trivial for your base class. This allows us to make class 
struct Cat {}; struct Leopard : Cat { int spots_ ; Leopard & operator = ( Leopard & ) = delete ; using Cat :: operator = ; }; static_assert ( is_trivially_copyable_v < Leopard > ); static_assert ( is_trivially_assignable_v < Leopard & , const Leopard &> ); void test ( Leopard & a , const Leopard & b ) { a = b ; // absolutely no data is copied } 
This is supremely problematic for the library-writer. Every STL vendor miscompiles 
I see no way to fix everybody’s 
5. Proposal
Basically, we propose to respecify all of the traits to conform with the library-writer’s intuition. (For each case below, read "can be" as "is known to the compiler to be able to be".)
- 
     is_trivially_constructible < T > T 
- 
     is_trivially_constructible < To , From > To From is_trivially_constructible < float , int > is_trivially_copy_constructible < Polymorphic > 
- 
     is_trivially_constructible < To , Args ... > Args ... 
- 
     is_trivially_destructible < T > T 
- 
     is_trivially_assignable < To , From > To From is_trivially_assignable < float & , int &> 
- 
     is_trivially_copyable < T > T 
constexpr bool implies ( bool a , bool b ) { return b || ! a ; } template < class T > inline constexpr bool is_trivially_copyable_v = implies ( is_constructible_v < T , T &> , is_trivially_constructible_v < T , T &> ) && implies ( is_constructible_v < T , T &&> , is_trivially_constructible_v < T , T &&> ) && implies ( is_constructible_v < T , const T &> , is_trivially_constructible_v < T , const T &> ) && implies ( is_constructible_v < T , const T &&> , is_trivially_constructible_v < T , const T &&> ) && implies ( is_assignable_v < T & , T &> , is_trivially_assignable_v < T & , T &> ) && implies ( is_assignable_v < T & , T &&> , is_trivially_assignable_v < T & , T &&> ) && implies ( is_assignable_v < T & , const T &> , is_trivially_assignable_v < T & , const T &> ) && implies ( is_assignable_v < T & , const T &&> , is_trivially_assignable_v < T & , const T &&> ) && implies ( is_destructible_v < T > , is_trivially_destructible_v < T > ); 
Each of the above definitions models the library-writer’s intuition that "
- trivially default-constructible (sic; read "default-initializable") 
- trivially value-initializable ([P2782]) 
- trivially (copy|move)-constructible 
- trivially (copy|move)-assignable 
- trivially destructible 
- trivially equality-comparable (Clang’s - __is_trivially_equality_comparable 
- trivially lexicographically-comparable (defined internally by libc++) 
- trivially swappable 
The wording might look like:
[is true if and only if]is_trivially_constructible < T , Args ... > isis_constructible_v < T , Args ... > trueand the variable definition for, as defined below, is known tois_constructible call no operation that is not trivialbe equivalent in its observable effects to a simple copy of the complete object representation (when) or to have no observable effects (whensizeof ...( Args ) == 1 ). Whensizeof ...( Args ) == 0 , yieldssizeof ...( Args ) >= 2 false; this usage is deprecated.[
is true if and only if]is_trivially_assignable < T , U > isis_assignable_v < T , U > trueand the assignment, as defined by, is known tois_assignable call no operation that is not trivialbe equivalent in its observable effects to a simple copy of the complete object representation.[
is true if and only if]is_trivially_destructible < T > isis_destructible_v < T > trueandisremove_all_extents_t < T > either a non-class type or a class type with a trivial destructora type whose destructor is known to have no observable effects.
Note: I haven’t found a way to break 
This proposal breaks a lot of eggs, but it makes a good omelet.
It would allow the following naïve code to Just Work, even though it is buggy today (Godbolt):
template < class T , class U > U * simple_copy ( T * first , T * last , U * dfirst ) { static_assert ( std :: is_assignable_v < T & , U &> ); if constexpr ( std :: is_trivially_assignable_v < T & , U &> ) { static_assert ( sizeof ( T ) == sizeof ( U )); std :: memmove ( dfirst , first , ( last - first ) * sizeof ( T )); return dfirst + ( last - first ); } else { while ( first != last ) * dfirst ++ = * first ++ ; } } int main () { int source [] = { 1 , 2 , 3 }; float dest [] = { 1 , 2 , 3 }; simple_copy ( source , source + 3 , dest ); printf ( "%f %f %f \n " , dest [ 0 ], dest [ 1 ], dest [ 2 ]); } 
Note: Okay, to really make 
This proposal opens the door for vendor-specific extensions to permit e.g. 
struct UniquePtr { int * p_ ; [[ clang :: trivial ]] explicit UniquePtr ( int * p ); ~ UniquePtr (); }; 
I believe this proposal completely eliminates all references to "trivial operations" and "trivial functions" (other than trivial special members). That’s nice, because the Standard never defines what those are. By removing all references to them, we never have to define what they are.
By permitting vendors to define 
Note: Non-
Finally, we need to do something with [basic.types.general]/2–3. Given the proposed wording above, I think we could just strike [basic.types.general]/2–3 as redundant: obviously, if a type is trivially copy-assignable by the new wording (let alone TC), then copying its object representation also copies its value, by definition. Striking those two paragraphs would also save us from having to define what the "[original] value" of an object means, standardese-wise.
[basic.types.general]/4 says: "For trivially copyable types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values." This sentence doesn’t seem useful, but also isn’t harmful; we can let it be.
5.1. Conclusion
This proposal is drastic and breaks a lot of eggs, but makes a good omelet. We would come out the other side with a type-traits library that is actually usable by library-writers. Library-writers are already quietly assuming that the traits work this way, so bringing the Standard into line with those assumptions would immediately close a lot of library bugs.
I plan to bring an R1 with formal wording, and hope to also gain some implementation experience in my fork of Clang. Assistance will be gratefully accepted.
6. Acknowledgments
Thanks to Ville Voutilainen and Giuseppe D’Angelo for their encouragement and discussion.
Thanks to the regulars on the cpplang Slack for rubber-ducking a lot of these examples.