C++ Logo

std-proposals

Advanced search

Re: [std-proposals] User-Defined Trivial Constructors

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Sat, 10 Jan 2026 13:39:52 -0500
On Sat, Jan 10, 2026 at 9:09 AM Jody Hagins via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> title: "User-Defined Trivial Constructors"
> [...]
> To be an implicit lifetime type, a class must, among other things, have at
> least one trivial constructor. The only way to explicitly specify a trivial
> constructor is via `= default`. But you can't only use `= default`
> user-defined constructors. Hence, no way for anything but the default,
> copy, and move constructors to be trivial.
>

Here's the actual X in your XY problem. You have a type — let's call it
`unique_ptr` — such that you want to be able to do things like this with it:

- **Shared memory** — inter-process communication via memory-mapped regions
> - **Memory-mapped files** — persistent data structures
> - **Custom allocators** — placement new into raw storage
> - **`std::start_lifetime_as`** — explicit lifetime management
>

All of these in the real world just require that you have some way of
getting the right object-representation into a certain memory location
(which of course you do), and then some way of convincing the compiler (or,
philosophically, convincing the abstract machine) that there is really an
object there already. That is, at runtime the abstract-machine program
doesn't need to *create* an object, it just needs to become *aware* of the
object that's already there.

Now, the problem is that you can do the above things only with
implicit-lifetime types. Or at least you think you can. Personally I'd like
to see the paper contain authoritative chapter-and-verse citations proving
that (1) implicit-lifetime-ness is *necessary* to do these various things
without UB, and even more importantly (2) implicit-lifetime-ness is
*sufficient* to do these various things without UB.

`unique_ptr` is not an implicit-lifetime type. Therefore (pending the above
chapter-and-verse citations), on paper, you can't do these things with
`unique_ptr`.
But of course *physically* you can do these things with `unique_ptr`.
So the paper spec fails to capture the actual behavior of the system. This
is a defect in the spec, in one of two places. Either:
(A) `unique_ptr` *should* be recognized as an implicit-lifetime type, or
(B) implicit-lifetime-ness *shouldn't* be considered a prerequisite for
doing these various things without UB.

Your proposal seems to assume (A), but depending on the outcome of your
chapter-and-verse citations, you might need to explain why you don't just
pursue (B) instead.
Anyway, OK, let's assume (A) for now.

Next you explain: "To be an implicit lifetime type, a class must, among
other things, have at least one trivial constructor."
But `unique_ptr` doesn't have any trivial constructors. *None* of its
special members are trivial. So — given that we're assuming (A) from above
— either:
(C) `unique_ptr` *should* gain a trivial constructor (that is not the
default, copy, or move constructor, because none of those are physically
trivial), or
(D) having at least one trivial constructor *shouldn't* be considered a
prerequisite for implicit-lifetime-ness.

Your proposal assumes (C), and then proposes a way to achieve (C). But
surely (D) is easier, both in terms of wording and in terms of the
implementation. (D) just requires altering one sentence
<https://eel.is/c++draft/class.prop#8> in the paper spec; vendors don't
have to change anything about their implementations at all. (They do have
to promise not to implement "optimizations" that would break the current
behavior; that is, today they technically have the freedom to exploit UB
here, which freedom (D) takes away from them. But they're not *using* that
freedom yet.)
Also, because (D) better matches the physical reality, I think it's
just *philosophically
preferable* to pursue (D).

You write:

> What we need is a way to have *both*: a safe default constructor *and* a
> trivial constructor for implicit lifetime purposes.


No, what you *need* is a way to have both a safe default constructor and
*implicit-lifetime-ness*. I don't think you've motivated the idea that you
need a trivial constructor, except that you've assumed not to pursue (D).

----
Now, my thoughts on the actual proposal/syntax.
    explicit Foo(std::trivial_t) = default;
> The semantics are straightforward:
> - `= default` on a user-defined constructor means "do nothing"
> - It is trivial — exactly what `Foo() = default` would do
- It is only valid if `Foo() = default` would produce a trivial default
> constructor
>
This part seems unintuitive to me. I see physically why you need it: your
proposal cannot deal with the snippet
    struct S {
      std::string m_;
      explicit S(std::trivial_t) = default;  // Defaulted, but also somehow
trivial??
    };
without becoming self-contradictory, so, you propose that this snippet
should be ill-formed.
You get into this self-contradiction because of your syntax-misuse.
`=default` does *not* mean "trivial" (that is, bitwise /
object-representation-wise). It means "memberwise" (which may be trivial
but also may not). You're trying to use `=default` as a syntactic signal
that means "trivial" — but it doesn't *work* as that signal, because
=default` doesn't mean "trivial" to begin with. It means "memberwise."
Relevant: P1029
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1029r3.pdf>
tried to introduce the syntax `X(X&&) = bitcopies;` as a new kind of signal
you can put to indicate that the function just copies the bits. (P1029
totally messed this up, mind you: it annotated the *move constructor* in
order to say something about the *relocation operation*. But it did have
the right idea insofar as that you can't use `=default` to mean "copies the
bits"; you need a new syntax instead.)
Now, getting to the possibly constructive portion of my comments... ;)
It seems to me that what you're asking for is at least *very close to*
something that Louis Dionne at libc++ has been wanting for quite a while:
libc++ wants a way to mark user-defined constructors as trivial.
My own pet example — likely different from Louis' examples — is:
    struct unique_ptr {
        int *p_;
        [[trivial]] explicit unique_ptr(int *p) : p_(p) {}
        ~~~~
    };
That is, the library author here is telling the compiler to report
`is_trivially_constructible_v<unique_ptr<int>, int*>`. This is, *physically*,
already true: the way you construct a unique_ptr from an int* is that you
copy the bits from the int* into the bits of the unique_ptr.  But without
the annotation, the compiler doesn't know this, and so for example you get
bad codegen for
    std::uninitialized_copy_n(array_of_intptr, 100, array_of_uniqueptr);
(Godbolt <https://godbolt.org/z/fe6ecsEWG>).
So, what we could really use is a way to set [[trivial]] on any
(single-argument) constructor. Also, it could be useful for debugging
purposes to be able to write
    struct X {
        int i_ = 0;
        [[trivial]] X(int i): i_(i) { assert(i_ != 0); }
    };
That is, "I warrant that this constructor is trivial for purposes of
optimization and ABI; but also, if you ever do need to generate a call to
it, here's what I'd like that call to do."
That is, "This type is basically an aggregate, but with a little extra
instrumentation."
This gets you the ability to do (C) from above (although for your purposes
I think you should want (D) instead).
It also gets library vendors something they've wanted for a while,
something that's generally useful, not just for this specific corner case.
It also doesn't abuse the `=default` syntax.
It applies also to other functions, such as operator==, operator<=>, and
swap.
(The attribute would probably want to be rejected with a warning on any
function that doesn't have an obvious "bitwise" analogue.)
Now, the downsides of the above idea:
- it's novel (but so is your syntax proposal: that's a tie).
- it relies on the effect of an attribute (which is *controversial*)
- it needs to be specced out a lot more than what I wrote above
Maybe Louis could confirm or deny that libc++ has applications for
"user-defined trivial" functions, and give some better examples than I did.
my $.02,
–Arthur

Received on 2026-01-10 18:40:09