[cfe-dev] Move constructor forces copy assignment to be implicitly defaulted?

Steve Ramsey clang at lucena.com
Mon May 28 11:15:19 PDT 2012


On May 28, 2012, at 10:28 AM, Howard Hinnant wrote:

> On May 28, 2012, at 2:52 AM, Suman Kar wrote:
> 
>>> Right.  I mean that they don't exist, just as in C++98/03.  Deleted move members are generally problematic as they inhibit "copying" from rvalues.  If you have valid copy members and deleted move members, you can't return such an object from a function.
>>> 
>> 
>> Okay, I still can't wrap my head around the last sentence. This looks ominous.
> 
> struct A
> {
>    A() = default;
>    A(const A&) = default;
>    A& operator=(const A&) = default;
>    A(A&&) = delete;
>    A& operator=(A&&) = delete;
> };
> 
> A
> make()
> {
>    A a;
>    return a;
> }
> 
> int main()
> {
>    A a = make();
> }
> 
> test.cpp:67:7: error: call to deleted constructor of 'A'
>    A a = make();
>      ^   ~~~~~~
> test.cpp:54:5: note: function has been explicitly marked deleted here
>    A(A&&) = delete;
>    ^
> 1 error generated.
> 
> But:
> 
> struct A
> {
>    A() = default;
>    A(const A&) = default;
>    A& operator=(const A&) = default;
> };
> 
> A
> make()
> {
>    A a;
>    return a;
> }
> 
> int main()
> {
>    A a = make();
> }
> 
> Compiles and runs fine.

I think it’s instructive to point out _why_ this is the case. In the original A, because the move special member functions have been explicitly deleted, a can’t be moved out as the return value. The compiler honors the explicit intent and also does not invoke the fallback copy behavior or the return value optimization (which would be a sly way to get around the move prohibition). In the second A, ignoring deprecated behavior, the move special member functions are implicitly declared and implicitly defined as deleted due to the presence of the explicitly declared copy special member functions. When make returns, it’s still not using the move constructor, but is allowed to use the copy constructor as a fallback behavior (though presumably not the return value optimization).

For added fun, if we had a class B that inherits from the original A, an equivalent makeB function would actually work. To wit:

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

struct B
   : public A
{
};

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

int main()
{
   B b = makeB();
}

This is because B has implicitly defined special member functions, and so when it goes to return b in MakeB, it calls its own move constructor as well as A’s default constructor, bypassing the move prohibition entirely.

			Steve





More information about the cfe-dev mailing list