Date: Fri, 23 Jan 2026 18:24:21 -0500
On Fri, Jan 23, 2026 at 5:20 PM Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> wrote:
> [...]
As an optimisation for B, I want to use 'memcpy' instead of running a
> loop with 'construct_at'. But in order to be able to determine when I
> can use this optimisation, I need a new trait. Right now the Standard
> only has 'is_trivially_copyable' for this purpose
Wrong.
C++ has many "is_trivially_fooable" type traits, for various verbs "foo".
They come in two flavors:
- Surgical traits: is_trivially_copy_constructible,
is_trivially_move_assignable, is_trivially_destructible, (Clang's
<https://reviews.llvm.org/D147175> is_trivially_equality_comparable,
Giuseppe's
<https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2023/p2782r0.html>
is_trivially_value_initializable, ...)
- Holistic traits: is_trivially_copyable, (P1144's
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p1144r13.html>
is_trivially_relocatable)
The "surgical" traits are — well, caveat: they're currently not exactly,
but they're basically *intended* to have been and *are currently used in
the wild* as — simple optimization gates. They're aimed at the
user-programmer who says, "Okay, my code wants to do this specific
operation on a type (e.g., copy-construct it; e.g. compare it for
equality). Can I get away with doing that specific operation on the *object
representation* (e.g. memcpy it; e.g. memcmp it) instead?" The poster
children for these traits are all in the STL:
- is_trivially_copy_assignable controls whether std::copy can memcpy
- is_trivially_move_assignable controls whether std::move
<https://en.cppreference.com/w/cpp/algorithm/move.html> can memcpy
- is_trivially_copy_constructible controls whether std::uninitialized_copy
can memcpy
- is_trivially_move_constructible controls whether std::uninitialized_move
can memcpy
- is_trivially_equality_comparable controls whether std::equal can memcmp
- is_trivially_default_constructible controls whether
std::uninitialized_default_construct can memset
- is_trivially_value_initializable controls whether
std::uninitialized_value_construct can memset
In your case, you are precisely asking "Can I replace copy-construction
with memcpy?" So you should ask is_trivially_copy_constructible_v<T>.
(Now, currently is_trivially_copy_constructible_v<T> reports false for
types with non-trivial destructors. This is the subject of LWG2827
<https://cplusplus.github.io/LWG/issue2827> and P2842R0
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2842r0.pdf>,
and falls under my "currently not exactly" caveat.)
The "holistic" traits capture complex holistic properties of a type. Right
now there's only one — is_trivially_copyable. This is aimed at the
user-programmer who says, "Okay, my code wants to do some complicated
rearrangement, copying, and destruction of a bunch of objects, and I don't
want to restrict myself as to exactly what *specific* operations I'm going
to pretend to do. I just want to know, can I get away with doing such a
complicated operation on the *object representations* of these objects?"
For example, I'm going to do something like std::partial_sort_copy — can I
copy and shuffle the objects as if they were just bags of bytes?
is_trivially_copyable tells me the answer.
is_trivially_relocatable (non-standard, but used by many libraries in real
life, including Abseil, Folly, HPX, Parlay, and Thrust) answers the same
question but specifically for complicated *affine (one-to-one)*
rearrangements of values. For example, I'm going to std::sort these
objects, or std::rotate them, or std::partition them — can I permute their
object representations instead of calling their assignment operators and
swap and so on? I'm going to reallocate this buffer of objects from buffer
A to buffer B — can I memcpy them instead of calling their move
constructors and destructors? I'm going to std::partial_sort_copy these
objects from A to B, and then destroy from A exactly those objects whose
values I've copied into B — can I do that just by shuffling bytes?
is_trivially_relocatable tells me the answer.
In particular, is_trivially_relocatable tells you whether you can *safely*
lower std::sort to std::qsort (although it doesn't help with the icky
impedance mismatch between sort's and qsort's comparator signatures).
As for the name of this new trait, well the word 'trivial' is no
> longer usable in any context in C++ because of the extreme ambiguity
> that has been given to it -- remember how we were gonna say an object
> was 'trivially relocatable' even if you needed to run an encryption
> algorithm after relocating it? And we called that encryption algorithm
> "restart_lifetime"? So forget about the word 'trivial', it's useless
> now.
>
FWIW, I disagree; I think the Kona vote proved that an overwhelming
majority of WG21 *rejected* the attempt to make "trivial" mean something
other than what it means; if anything, we can say that the meaning it's
already got in popular usage has been *recently reaffirmed*.
Compare [class.union.general]
<https://eel.is/c++draft/class.union.general#note-3> and
[class.copy.ctor]/10.1 <https://eel.is/c++draft/class.copy.ctor#10.1>,
where "trivial" is used with exactly the "bitwise" meaning; and P3074
"Trivial Unions"
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3074r7.html>
(adopted for C++26), where "trivial destructor" is used to mean exactly
"destructor that does nothing" (not, e.g. "destructor that does encryption
algorithms").
[...] Let's call this new trait: is_memcpyable
>
No. That's like calling a mushroom is_edible ("Any mushroom is edible...
once"), or a spirit from the vasty deep is_callable ("But will they come
when you do call for them?"). Any typ*e is memcpyable*; but what does it do
when you do memcpy it?
If memcpying it is a practical substitute for copy-constructing it, then it
is *trivially copy constructible*.
If memcpying it is a practical substitute for move-assigning it, then
it is *trivially
move assignable*.
We already have these traits. Admittedly we (WG21) desperately need to
clean them up around the edges, but we do already *have* them. You (the
user-programmer) just have to start using them.
Now, this does leave you with the question: "Should
`is_trivially_copy_constructible_v<Poly>` ever be true?" Sadly the answer
is no, because a polymorphic type can have a copy constructor that changes
the vptr. I'm not talking about ARM64e stuff here — just simple inheritance.
struct Animal { virtual int f(); };
struct Cat : Animal { int f() override; };
const Animal& a = Cat();
Animal b = a;
Copying from `a` to `b` isn't a simple memcpy on *any* platform. (Godbolt.
<https://godbolt.org/z/nGbYd8Yo1>)
assert(memcmp(&a, &b, sizeof(Animal)) != 0); // invariably true
HTH,
Arthur
Std-Proposals <std-proposals_at_[hidden]> wrote:
> [...]
As an optimisation for B, I want to use 'memcpy' instead of running a
> loop with 'construct_at'. But in order to be able to determine when I
> can use this optimisation, I need a new trait. Right now the Standard
> only has 'is_trivially_copyable' for this purpose
Wrong.
C++ has many "is_trivially_fooable" type traits, for various verbs "foo".
They come in two flavors:
- Surgical traits: is_trivially_copy_constructible,
is_trivially_move_assignable, is_trivially_destructible, (Clang's
<https://reviews.llvm.org/D147175> is_trivially_equality_comparable,
Giuseppe's
<https://www.open-std.org/JTC1/SC22/WG21/docs/papers/2023/p2782r0.html>
is_trivially_value_initializable, ...)
- Holistic traits: is_trivially_copyable, (P1144's
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p1144r13.html>
is_trivially_relocatable)
The "surgical" traits are — well, caveat: they're currently not exactly,
but they're basically *intended* to have been and *are currently used in
the wild* as — simple optimization gates. They're aimed at the
user-programmer who says, "Okay, my code wants to do this specific
operation on a type (e.g., copy-construct it; e.g. compare it for
equality). Can I get away with doing that specific operation on the *object
representation* (e.g. memcpy it; e.g. memcmp it) instead?" The poster
children for these traits are all in the STL:
- is_trivially_copy_assignable controls whether std::copy can memcpy
- is_trivially_move_assignable controls whether std::move
<https://en.cppreference.com/w/cpp/algorithm/move.html> can memcpy
- is_trivially_copy_constructible controls whether std::uninitialized_copy
can memcpy
- is_trivially_move_constructible controls whether std::uninitialized_move
can memcpy
- is_trivially_equality_comparable controls whether std::equal can memcmp
- is_trivially_default_constructible controls whether
std::uninitialized_default_construct can memset
- is_trivially_value_initializable controls whether
std::uninitialized_value_construct can memset
In your case, you are precisely asking "Can I replace copy-construction
with memcpy?" So you should ask is_trivially_copy_constructible_v<T>.
(Now, currently is_trivially_copy_constructible_v<T> reports false for
types with non-trivial destructors. This is the subject of LWG2827
<https://cplusplus.github.io/LWG/issue2827> and P2842R0
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2842r0.pdf>,
and falls under my "currently not exactly" caveat.)
The "holistic" traits capture complex holistic properties of a type. Right
now there's only one — is_trivially_copyable. This is aimed at the
user-programmer who says, "Okay, my code wants to do some complicated
rearrangement, copying, and destruction of a bunch of objects, and I don't
want to restrict myself as to exactly what *specific* operations I'm going
to pretend to do. I just want to know, can I get away with doing such a
complicated operation on the *object representations* of these objects?"
For example, I'm going to do something like std::partial_sort_copy — can I
copy and shuffle the objects as if they were just bags of bytes?
is_trivially_copyable tells me the answer.
is_trivially_relocatable (non-standard, but used by many libraries in real
life, including Abseil, Folly, HPX, Parlay, and Thrust) answers the same
question but specifically for complicated *affine (one-to-one)*
rearrangements of values. For example, I'm going to std::sort these
objects, or std::rotate them, or std::partition them — can I permute their
object representations instead of calling their assignment operators and
swap and so on? I'm going to reallocate this buffer of objects from buffer
A to buffer B — can I memcpy them instead of calling their move
constructors and destructors? I'm going to std::partial_sort_copy these
objects from A to B, and then destroy from A exactly those objects whose
values I've copied into B — can I do that just by shuffling bytes?
is_trivially_relocatable tells me the answer.
In particular, is_trivially_relocatable tells you whether you can *safely*
lower std::sort to std::qsort (although it doesn't help with the icky
impedance mismatch between sort's and qsort's comparator signatures).
As for the name of this new trait, well the word 'trivial' is no
> longer usable in any context in C++ because of the extreme ambiguity
> that has been given to it -- remember how we were gonna say an object
> was 'trivially relocatable' even if you needed to run an encryption
> algorithm after relocating it? And we called that encryption algorithm
> "restart_lifetime"? So forget about the word 'trivial', it's useless
> now.
>
FWIW, I disagree; I think the Kona vote proved that an overwhelming
majority of WG21 *rejected* the attempt to make "trivial" mean something
other than what it means; if anything, we can say that the meaning it's
already got in popular usage has been *recently reaffirmed*.
Compare [class.union.general]
<https://eel.is/c++draft/class.union.general#note-3> and
[class.copy.ctor]/10.1 <https://eel.is/c++draft/class.copy.ctor#10.1>,
where "trivial" is used with exactly the "bitwise" meaning; and P3074
"Trivial Unions"
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3074r7.html>
(adopted for C++26), where "trivial destructor" is used to mean exactly
"destructor that does nothing" (not, e.g. "destructor that does encryption
algorithms").
[...] Let's call this new trait: is_memcpyable
>
No. That's like calling a mushroom is_edible ("Any mushroom is edible...
once"), or a spirit from the vasty deep is_callable ("But will they come
when you do call for them?"). Any typ*e is memcpyable*; but what does it do
when you do memcpy it?
If memcpying it is a practical substitute for copy-constructing it, then it
is *trivially copy constructible*.
If memcpying it is a practical substitute for move-assigning it, then
it is *trivially
move assignable*.
We already have these traits. Admittedly we (WG21) desperately need to
clean them up around the edges, but we do already *have* them. You (the
user-programmer) just have to start using them.
Now, this does leave you with the question: "Should
`is_trivially_copy_constructible_v<Poly>` ever be true?" Sadly the answer
is no, because a polymorphic type can have a copy constructor that changes
the vptr. I'm not talking about ARM64e stuff here — just simple inheritance.
struct Animal { virtual int f(); };
struct Cat : Animal { int f() override; };
const Animal& a = Cat();
Animal b = a;
Copying from `a` to `b` isn't a simple memcpy on *any* platform. (Godbolt.
<https://godbolt.org/z/nGbYd8Yo1>)
assert(memcmp(&a, &b, sizeof(Animal)) != 0); // invariably true
HTH,
Arthur
Received on 2026-01-23 23:24:39
