Search code examples
c++abiitanium

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


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.


Solution

  • 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)