How to mark a C++ type as not "trivially_copyable", while keeping it "trivial for the purposes of calls" in the Itanium C++ ABI?

62 views Asked by At

I want my C++ type to expose a move-only interface, therefore I declared its copy constructor and copy assignment as deleted. Nonetheless, the move constructor and move assignment are trivial, and the destructor is also trivial, therefore it is considered as "trivially_copyable" for the C++ standard. This is a problem, because copying an object of this type really is semantically wrong.

A possible solution is to user-define an empty destructor that does nothing. This makes the type not "trivially_copyable", but as a consequence the type is not longer "trivial for the purposes of calls" for the Itanium ABI, which can have negative performance implications.

Is there a way to achieve all these goals at the same time? The type should have trivial move-constructor, move-assignment and destructor. The type should not be trivially copyable. The type should be trivial for the purpose of calls for the Itanium ABI.

2

There are 2 answers

0
Artyer On BEST ANSWER

https://itanium-cxx-abi.github.io/cxx-abi/abi.html#non-trivial

A type is considered non-trivial for the purposes of calls if:

  • it has a non-trivial copy constructor, move constructor, or destructor, or
  • all of its copy and move constructors are deleted.

[class.prop]p1:

A trivially copyable class is a class:

  • that has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator,
  • where each eligible copy constructor, move constructor, copy assignment operator, and move assignment operator is trivial, and
  • that has a trivial, non-deleted destructor.

So all the copy/move constructors and the destructor have to remain non-trivial, so you can only make it not trivially copyable with a copy or move assignment operator. Since you want the move assignment operator to be trivial, that leaves a copy assignment operator:

struct T {
    T(T&&) = default;
    T(const T&) = delete;
    T& operator=(T&&) = default;
    T& operator=(const T&) = delete;
    ~T() = default;
private:
    [[deprecated("To ensure T is not trivially copyable; should not be used")]] void operator=(const volatile T&);
};

This can also be solved with [[clang::trivial_abi]]:

struct [[clang::trivial_abi]] T {
    constexpr T(T&&) noexcept;
    T(const T&) = delete;
    T& operator=(T&&) = default;
    T& operator=(const T&) = delete;
    ~T() = default;
};

constexpr T::T(T&&) noexcept = default;

But that is only available with Clang (which modifies the definition of "non-trivial for the purposes of calls" to exclude classes with this attribute)

0
user17732522 On

You can make the move constructor trivial, but the move assignment non-trivial, i.e.

struct MyType {
    //...
    MyType(MyType&&) noexcept = default;
    inline MyType& operator=(MyType&&) noexcept;
    //...
};
inline MyType& MyType::operator=(MyType&&) noexcept = default;

For a type to be trivially-copyable all eligible non-deleted constructors, destructors and assignment operators must be trivial. However, the trivial for the purpose of calls property looks only at constructors and destructors.

The type should have trivial move-constructor, move-assignment and destructor.

No, this requirement makes it impossible. You are basically asking the type to be trivially-copyable without it being trivially-copyable. Your requirements are basically the definition of that property.