Triskeldeian
Triskeldeian

Reputation: 628

Automatically generated move constructor with not movable members

I got in a situation which is quite interesting as the code I'm working on compiles even though I'm surprised it does so I would like to ask you for your take.

The situation is this. I have a class with deleted move and copy constructors, which has user-defined assignment operators:

struct A {
    A() { }
    A(const A&) = delete;
    A(A&& ) = delete;

    A& operator=(const A& ) { return *this; }
    A& operator=(A&& ) { return *this; }
};

And I have another class with A as the only member. In this class I defined the copy constructor but I kept the move constructor as default and defined the assignment operator through a call to the swap function:

class B{
public:
    A a;

    B()
    : a{}
    { }

    B(const B&)
    : a{}
    { }

    B(B&& other) = default;
};

int main() {
    B b1;
    B b2(std::move(b1)); // compiles??
}

Why does the default move constructor work, considering that it cannot simply call the move or copy constructor A? I am using gcc 4.8.4.

Upvotes: 6

Views: 539

Answers (1)

Barry
Barry

Reputation: 303890

My original answer was wrong, so I'm starting over.


In [class.copy], we have:

A defaulted copy/ move constructor for a class X is defined as deleted (8.4.3) if X has:
— [...]
— a potentially constructed subobject type M (or array thereof) that cannot be copied/moved because overload resolution (13.3), as applied to M’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,
— [...]

That bullet point applies to B(B&& other) = default;, so that move constructor is defined as deleted. This would seem to break compilation with std::move(), but we also have (via resolution of defect 1402):

A defaulted move constructor that is defined as deleted is ignored by overload resolution (13.3, 13.4). [ Note: A deleted move constructor would otherwise interfere with initialization from an rvalue which can use the copy constructor instead. —end note ]

Ignoring is the key. Thus, when we do:

B b1;
B b2(std::move(b1));

Even though the move constructor for B is deleted, this code is well-formed because the move constructor simply doesn't participate in overload resolution and the copy constructor is called instead. Thus, B is MoveConstructible - even if you cannot construct it via its move constructor.

Upvotes: 7

Related Questions