short

A type that is trivially copyable and cannot be copied

· english· audience: working-cpp· AI-generated, reviewed by Filip Sajdak

Here is a type. On a conforming compiler, all three of these assertions hold:

static_assert(std::is_trivially_copyable_v<Nutshell>);
static_assert(!std::is_copy_constructible_v<Nutshell>);
static_assert(!std::is_copy_assignable_v<Nutshell>);

Trivially copyable, and you cannot copy it.

The type

From P3279R0, Arthur O’Dwyer’s paper on what “trivially fooable” ought to mean:

struct Hamlet {
    Hamlet(const Hamlet&) { }                    // user-provided: not trivial
    Hamlet(Hamlet&&) = default;
    Hamlet& operator=(Hamlet&&) = default;
};

struct Nutshell {
    Hamlet h_;
    Nutshell(Nutshell&&) = default;
    Nutshell& operator=(Nutshell&&) = default;
};

The mechanism is easy to miss. Declaring the move operations suppresses the copy operations. So Nutshell has no eligible copy constructor and no eligible copy assignment at all. The standard’s definition asks whether every eligible copy or move operation is trivial. With the copies gone, only the trivial moves remain. The condition holds because nothing is left to fail.

Hamlet is not trivially copyable. Nutshell, which contains one, is.

The divergence, still live

The paper reports that EDG and MSVC say true and Clang and GCC say false. It states plainly: “EDG+MSVC are certainly correct according to the Standard.” That was May 2024. Two years on:

GCC 16.1 Clang 22.1 MSVC 19.51
is_trivially_copyable_v<Nutshell> false false true

Two compilers give the answer people expect and the standard does not ask for. The one that follows the wording stands alone.

What the retelling gets wrong

This case travels as “a trivially copyable type with a non-trivially-copyable member”. That makes it sound as if any aggregate would do. It would not:

struct PlainPair { Hamlet a; Hamlet b; };   // false on GCC, Clang AND MSVC

A plain aggregate of non-trivially-copyable members is not trivially copyable anywhere, MSVC included. The defaulted move declarations do all the work. Drop them from the example and the effect disappears. Check before repeating it, because the short version is memorable and wrong.

Why the trait behaves this way

is_trivially_copyable is not asking “can I copy this cheaply”. It asks a structural question about which operations are eligible and whether those are trivial. A type with no copy operations at all passes on a technicality.

O’Dwyer’s proposal redefines the triviality traits to mean what library authors read them to mean. Every supported value-semantic operation must be trivial. The paper phrases this as implications rather than a list of eligibility conditions. Under that definition a type you cannot copy would not claim to be trivially copyable.

The practical takeaway is smaller and more immediate. The trait is_trivially_copyable_v is not a licence to memcpy a value unless you have also checked that its type supports copying. If you dispatch on that trait in a container or a serialiser, pair it with is_copy_constructible_v. Then you will not care how the committee resolves this.


Sources: