Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is move constructor not picked when returning a local object of type derived from the function's return type?

The following code is rejected by both Clang and GCC (trunk versions):

#include <memory>

struct Base 
{
    Base() = default; 
    Base(Base const&) = delete;
    Base(Base&&) = default;
};

struct Derived : Base
{
    Derived() = default; 
    Derived(Derived const&) = delete;
    Derived(Derived&&) = default;
};    

auto foo()
    -> Base
{
    Derived d;    
    return d;   // ERROR HERE
}

Causing the following error:

prog.cc: In function 'Base foo()': prog.cc:21:12: error: use of deleted function 'Base::Base(const Base&)'
     return d;
            ^

According to [class.copy]/32:

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue

If the sentence above is meant to be parsed as (copy elision criteria met && lvalue) || (id-expression designating an automatic object), as this CWG defect seems to indicate, why isn't the last condition applying here? Is there a compiler bug both in Clang and GCC?

On the other hand, if the sentence is meant to be parsed as (copy elision criteria met && (lvalue || id-expression designating an automatic object)), isn't this a very misleading wording worth a DR?

like image 589
Andy Prowl Avatar asked Oct 14 '16 09:10

Andy Prowl


People also ask

Are move constructors automatically generated?

If a copy constructor, copy-assignment operator, move constructor, move-assignment operator, or destructor is explicitly declared, then: No move constructor is automatically generated. No move-assignment operator is automatically generated.

Why do we need move constructor?

A move constructor enables the resources owned by an rvalue object to be moved into an lvalue without copying. For more information about move semantics, see Rvalue Reference Declarator: &&. This topic builds upon the following C++ class, MemoryBlock , which manages a memory buffer.

What does std :: move () do?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

What is the difference between a move constructor and a copy constructor?

Move constructor moves the resources in the heap, i.e., unlike copy constructors which copy the data of the existing object and assigning it to the new object move constructor just makes the pointer of the declared object to point to the data of temporary object and nulls out the pointer of the temporary objects.


1 Answers

[class.copy]/32 continues:

[...] if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

The first overload resolution, treating d as an rvalue, selects Base::Base(Base&&). The type of the first parameter of the selected constructor is, however, not Derived&& but Base&&, so the result of that overload resolution is discarded and you perform overload resolution again, treating d as an lvalue. That second overload resolution selects the deleted copy constructor.

like image 162
T.C. Avatar answered Sep 28 '22 08:09

T.C.