How am I misunderstanding the C++ standard regarding "trivially copyable"?

Viewed 244

The static assert in the code below fails.

Of course, I know that this is because of the user-provided move constructor in Bar.

#include <type_traits>

using namespace std;

struct Bar
{
public:
    Bar()               = default;
    Bar(const Bar&)     = default;
    ~Bar()              = default;

    Bar(Bar&&)
    {
    }
};

struct Foo
{
    Bar b;

    Foo()                           = default;
   ~Foo()                           = default;
    Foo(const Foo&)                 = default;
    Foo(Foo&&)                      = delete;
    Foo & operator= (Foo && )       = delete;
    Foo & operator= (const Foo & )  = delete;
};


static_assert(is_trivially_copyable_v<Foo>);  // Fails

What I don't understand is how to interpret the standard regarding trivial copyability. From the C++ 17 standard:

A trivially copyable class is a class:

(6.1) where each copy constructor, move constructor, copy assignment operator, and move assignment operator ([class.copy], [over.ass]) is either deleted or trivial,

(6.2) that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and

(6.3) that has a trivial, non-deleted destructor.

It seems to me the Foo meets these criteria:

6.1: They're all deleted except the copy constructor which is trivial. Bar also has a trivial copy constuctor, so that should ensure the Foo copy contructor is truly trivial.

6.2: The defaulted copy constructor

6.3: Foo destructor is defaulted and so is the Bar one, so the destructor should be trivial.

What am I not getting?

By the way I'm not actually trying to do anything apart from better understand the standard.

2 Answers

Except I miss something fundamentally further on here, I think there's currently no actual vague statement about this within the standard, it's actually quite straight forward on my opinion and that means, you're right. Your class Foo should be trivially copyable.

Beginning at top-level for your class Foo:

class6 says:

A trivially copyable class is a class:

(6.1) where each copy constructor, move constructor, copy assignment operator, and move assignment operator ([class.copy], [over.ass]) is either deleted or trivial,

(6.2) that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and

(6.3) that has a trivial, non-deleted destructor.

Points 2) and 3) are trivial and fulfilled.

Point 1): The only constructor of relevance here, that is not deleted, is the copy constructor. So is it trivial?

class.copy#ctor-11 answers that:

11

A copy/move constructor for class X is trivial if it is not user-provided and if:

(11.1) class X has no virtual functions and no virtual base classes, and

(11.2) the constructor selected to copy/move each direct base class subobject is trivial, and

(11.3) for each non-static data member of X that is of class type (or array thereof), the constructor selected to copy/move that member is trivial;

otherwise the copy/move constructor is non-trivial.

Again, points 1) and 2) are trivial and fulfilled. For point 3, the standard uses the terminus "copy/move", not copy and move, emphasizing the dual distinguished orthogonal(!) nature here (common and straight forward scheme within the drafts). Since there's a trivial non-deleted copy constructor available for Bar, there's no chance for any trouble here reading the standard strictly, since how should the selected copy constructor for the bar member be the user-defined move constructor (see memberwise copy for the default copy constructor)? The "decay" to the copy-construction way if no selectable move constructor could be found the other way around would be plausible though, but that's not the case here.

(6.1) where each copy constructor, move constructor, copy assignment operator, and move assignment operator ([class.copy], [over.ass]) is either deleted or trivial.

A user defined constructor is not trivial. Therefore your user defined move constructor makes your object non trivial.

More here: What is a non-trivial constructor in C++?

Related