Move constructor forces copy assignment to be implicitly defaulted?

A quick question:

struct A {};

struct B : A { ~B() {} };

A makeA() {
A a;
return a;
}

B makeB() {
B b;
return b;
}

int main() {
A a = makeA();
B b = makeB();
}

Is it correct to assume that A will be moved and B will not? Should a
diagnostic be required for the call to makeB?

I’m real glad you asked that. This plays into 12.8 p7 from the FDIS:

If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.

This paragraph features in my nightmares. I go back every few weeks and try and parse it again, but I still get confused. In my current interpretation, it’s saying that a copy constructor will always be declared, whether implicitly or explicitly; any implicitly declared copy constructor will always be defined as deleted unless there are no implicitly or explicitly declared move special member functions.

“But, wait,” you’re thinking, “didn’t I ask about the move constructor?” Well, yes. B’s move constructor will not be declared at all because of the presence of a user-declared destructor, and hence will not be defined. However, B is still technically move-constructible because it will fall back to the implicitly defined copy constructor (as per the previously mentioned Note in 12.8 p9), and that copy constructor exists because of 12.8 p7.

      Steve

Actually, before Howard hits me with the Language Lawyer Bat again, I should add a clarification to the end of that last sentence: “otherwise it will be defined as defaulted.” That bit is important because the difference between being “defined” and being “defined as defaulted” is that “defaulted” can mean “deleted” depending on the circumstances.

      Steve

A quick question:

struct A {};

struct B : A { ~B() {} };

A makeA() {
A a;
return a;
}

B makeB() {
B b;
return b;
}

int main() {
A a = makeA();
B b = makeB();
}

Is it correct to assume that A will be moved and B will not? Should a
diagnostic be required for the call to makeB?

[snip explanation]

However, B is still technically move-constructible because it will fall back to the implicitly defined copy constructor (as per the previously mentioned Note in 12.8 p9), and that copy constructor exists because of 12.8 p7.

While I understand the previous bit I'm stumped with the 'technically
move constructible' bit. Can you elaborate?

Also, I wasn't very sure about the 'default can mean delete' bit. So,
thanks for clarifying that. I'm sure I'll be getting back to this
thread and the draft in about a weeks time again!

Regards,
Suman

Being move-constructible doesn't imply that something has a move constructor; thanks to the Note in 12.8 p9, anything that can't be moved can always be copied, assuming it is copy-constructible. Determining whether a given move operation will result in a move or fall back to a copy requires a bit more work than simply checking std::is_move_constructible (or std::is_nothrow_move_constructible).

      Steve