15

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.

AILien
  • 832
  • 4
  • 11
  • 2
    6.1 `Bar`'s move constructor is not trivial as you've noticed yourself? – Ted Lyngmo May 26 '21 at 10:17
  • 1
    But that's Bar's move constructor. The standard says Foo's move constructor must be deleted or trivial - it's deleted. I can see that Bar's move constructor stops Foo's move constructor from being trivial. But according to the standard Foo's move constructor being trivial is not required if it's deleted. – AILien May 26 '21 at 10:34
  • 1
    what I read here: https://en.cppreference.com/w/cpp/language/move_constructor is that `Foo`s move constructor is not considered trivial, because `Bar`s move constructor isnt. I am a little puzzled why thats the case when `Foo`s move constructor is deleted, but thats how I understand cppref – 463035818_is_not_an_ai May 26 '21 at 10:40
  • 1
    __Trivial move constructor__ - _"...the move constructor selected for every non-static class type (or array of class type) member of T is trivial...."_ https://en.cppreference.com/w/cpp/language/move_constructor#Trivial_move_constructor – Richard Critten May 26 '21 at 11:41
  • 2
    Compiles fine in [msvc](https://gcc.godbolt.org/z/qGber78sW)... anyway the relevant bit seems to be [class.copy.ctor-11](https://timsong-cpp.github.io/cppwp/n4659/class.copy#ctor-11), which IMO is a bit vague (or not?). – rustyx May 26 '21 at 11:48
  • 2
    MSVC Changes it's mind after version 19.26 - https://godbolt.org/z/1dbfv7sjd – Richard Critten May 26 '21 at 11:52
  • 2
    @RichardCritten that links describes whether the *move constructor* is trivial. I would agree that Foo's move constructor is not trivial. But for *Foo* to be trivially_copyable the standard (https://timsong-cpp.github.io/cppwp/n4659/class#6) seems to say that the move constructor being trivial is not relevant if it's deleted. – AILien May 26 '21 at 12:18

2 Answers2

2

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.

Secundi
  • 1,150
  • 1
  • 4
  • 12
0

(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++?

Giel
  • 397
  • 1
  • 9
  • 2
    That doesn't add new things here to the discussion. You're talking about the Bar class, but it's the Foo class that is on focus here. The question is, why the deleted move constructor of Foo still seems to be relevant. – Secundi Dec 07 '21 at 09:56
  • Bar is part of foo, if Bar is not trivial, neither will Foo be – Giel Dec 17 '21 at 13:14
  • 1
    then please provide the relevant standard sections for that! Strictly reading all relevant parts about that don't prove your assumption as far as I can see. – Secundi Dec 17 '21 at 14:18
  • PS: The question was about trivially copyable, not trivial class. – Secundi Dec 17 '21 at 14:20
  • My bad, I was not aware of the trivially copyable - https://en.cppreference.com/w/cpp/types/is_trivially_copyable seems to suggest it would be possible if no virtual is provided – Giel Dec 30 '21 at 01:25