c++language-lawyerc++20compiler-bugtrivially-copyable

Class isn't trivially_copyable if a constraint on its assignment operator is not satisfied with clang 16


Example code as below or on godbolt. clang 16/trunk believes S<int> is not a trivially_copyable class. clang 15, gcc trunk and MSVC believe otherwise.

#include <type_traits>

template<typename T>
struct S {
    T m_t;
    S(S const&) = default;
    S(S&&) = default;
    S& operator=(S const&) requires (!std::is_integral<T>::value) = default;
    ~S() = default;
};

// next five assertions pass for all compilers
static_assert(std::is_trivially_destructible<S<int>>::value);
static_assert(std::is_trivially_copy_constructible<S<int>>::value);
static_assert(std::is_trivially_move_constructible<S<int>>::value);
static_assert(!std::is_copy_assignable<S<int>>::value);
static_assert(!std::is_move_assignable<S<int>>::value);

// compiles with gcc trunk, MSVC and clang 15, fails with clang 16/trunk
static_assert(std::is_trivially_copyable<S<int>>::value);

According to the standard class.prop:

A trivially copyable class is a class:

  • that has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator ([special],
    [class.copy.ctor], [class.copy.assign]),

  • where each eligible copy constructor, move constructor, copy assignment operator, and move assignment operator is trivial, and

  • that has a trivial, non-deleted destructor ([class.dtor]).

S<int> has trivial copy/move constructor and trivial destructor. Its copy/move assignment operators are not eligible. I would agree with gcc/MSVC/clang15 on this. Is clang 16/trunk wrong on this one or am I missing something?

Edit: This is a confirmed clang bug.


Solution

  • Yes, S<int> is trivially copyable. It looks like you've discovered a clang bug. I was unable to find it in:

    So this may be a new regression.

    Proof for S<int> being trivially copyable

    It's worth examining what elegible means:

    An eligible special member function is a special member function for which:

    • the function is not deleted,
    • the associated constraints ([temp.constr]), if any, are satisfied, and
    • no special member function of the same kind is more constrained ([temp.constr.order]).

    - [class.prop] §1

    S& operator=(S const&) requires (!std::is_integral<T>::value) = default
    

    This copy assignment operator is user-declared (and not user-provided) and its constraints aren't satisfied, so it is not eligible. Furthermore:

    If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly declared as defaulted if and only if

    • X does not have a user-declared copy constructor,
    • [...]

    - [class.copy.assign] $4

    This means that:

    All of the remaining (eligible) special member functions are trivial, therefore S<int> is trivially copyable.