c++language-lawyerc++20c++-conceptstrivially-copyable

`std::is_trivially_copyable` disagreement between GCC and Clang for type wrapper


Consider the following move-only type:

struct MoveOnly
{
    MoveOnly()  = default;
    ~MoveOnly() = default;

    MoveOnly(const MoveOnly&)            = delete;
    MoveOnly& operator=(const MoveOnly&) = delete;

    MoveOnly(MoveOnly&&) noexcept            = default;
    MoveOnly& operator=(MoveOnly&&) noexcept = default;
};

Both GCC and Clang agree on:

static_assert(not std::is_trivially_copy_constructible_v<MoveOnly>);
static_assert(not std::is_copy_constructible_v<MoveOnly>);
static_assert(    std::is_trivially_copyable_v<MoveOnly>);

Now consider this wrapper type that propagates trivial copy construcibility:

template <typename T>
struct Wrapper
{
    Wrapper(const Wrapper&)
        requires(!std::is_trivially_copy_constructible_v<T> && std::is_copy_constructible_v<T>) 
    {
    }

    Wrapper(const Wrapper&)
        requires(std::is_trivially_copy_constructible_v<T>) = default;
};

Both GCC and Clang agree on:

static_assert(not std::is_trivially_copy_constructible_v<Wrapper<MoveOnly>>);
static_assert(not std::is_copy_constructible_v<Wrapper<MoveOnly>>);

However, GCC believes the following is true, while Clang believes it's false:

static_assert(    std::is_trivially_copyable_v<Wrapper<MoveOnly>>);
    // passes on GCC, fails on Clang

What compiler is correct here and why?

live example on Compiler Explorer


Solution

  • The type Wrapper<MoveOnly> is trivially-copyable.

    Wrapper<MoveOnly> has no eligible copy constructor because both of your declared ones have constraints that are not satisfied.

    It also has no move constructor, move assignment operator or additional implicitly-declared copy constructor because you declared copy constructors yourself.

    It does however have one eligible copy assignment operator. This one is implicitly-declared because you didn't declare any copy assignment operator yourself. It is also defaulted, not defined as deleted and trivial. (However, this implicit declaration of the copy assignment operator if a user-declared copy constructor is present has been deprecated since C++11.)

    So there exists at least one eligible copy/move constructor/assignment operator and all of these are trivial. Wrapper<MoveOnly> also has a trivial, non-deleted destructor. So all requirements for trivial-copyability are satisfied.

    (If you search for is:issue is:open trivially-copyable label:clang:frontend on LLVM's issue tracker you'll find multiple open issues where Clang currently incorrectly decides trivial-copyability and there are open CWG issues as well.)