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

Howard Hinnant hhinnant at apple.com
Mon May 28 11:32:05 PDT 2012


On May 28, 2012, at 2:15 PM, Steve Ramsey wrote:

> 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).

Right.

> 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.

Wrong.  12.8 [class.copy]/p9:

> 9 If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
> 
>  * X does not have a user-declared copy constructor,
>  ...

A has a user-declared copy constructor therefore the compiler will not implicitly declare a move constructor (and ditto for the move assignment operator).


> 
> 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).

The compiler isn't using A's move constructor because A doesn't have one.  However your conclusion is correct:  The compiler uses A's copy constructor.

> 
> 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,

B has special copy members, but not special move members.  12.8 [class.copy]/p9 (again):

> 9 If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if
>  ...
>  * the move constructor would not be implicitly defined as deleted.

And p11:

> A defaulted copy/ move constructor for a class X is defined as deleted (8.4.3) if X has:
>  ...
>  * a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3),
>    as applied to B’s corresponding constructor, results in an ambiguity or a function that is deleted
>    or inaccessible from the defaulted constructor,
>  ...

B doesn't have an implicit move constructor because if it did it would be implicitly deleted (p9) because it would call A's deleted move constructor (p11).

>  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.

When B returns its copy constructor is used because there is no move constructor.

Howard





More information about the cfe-dev mailing list