Trivial, but not trivially default constructible

I just watched Jason Turner’s CppCon 2023 talk “Great C++ is_trivial.” Circa 41m00s he picks on the wording of [class.prop]/2:

A trivial class is a class that is trivially copyable and has one or more eligible default constructors, all of which are trivial.

“That phrasing’s always bothered me. How can you have more than one default constructor? They could be constrained by requires-clauses… but then not more than one of them is eligible.”

Here’s the trick: Jason, you were wrongly assuming that a trivial class must be default-constructible at all! Godbolt:

template<class T>
struct S {
    S() requires (sizeof(T) > 3) = default;
    S() requires (sizeof(T) < 5) = default;

static_assert(not std::is_default_constructible_v<S<int>>);

Here S<int> is trivial, but, because it has two eligible default constructors, any attempt to default-construct an S<int> will fail due to overload resolution ambiguity. Thus S<int> is not default-constructible at all (and certainly not trivially default-constructible); but it still satisfies all the requirements of a trivial class.

By the way, it seems to me that Jason buries the lede when it comes to actually explaining triviality. The rule is: A type T is trivially fooable if and only if its foo operation is (known to the compiler to be) equivalent to the same foo operation on T’s object representation (i.e., an array of unsigned char). T is trivially copy-constructible if its copy constructor merely copies its bytes; it’s trivially default-constructible if its default constructor merely default-initializes its bytes (recall that default-initializing an array of unsigned char is a no-op); it’s trivially value-initializable (see “PSA: Value-initialization is not merely default-construction” (2023-06-22)) if its value initialization merely value-initializes its bytes (to zero); and so on. That’s the big thing to remember about the adverb “trivially” — it means “as if on the object representation.”

Posted 2024-04-02