Logo Questions Linux Laravel Mysql Ubuntu Git Menu

polymorphic unique_ptr copy elision




I have the following code that works on Clang 5.0 but not on Clang 3.8, with C++14 enabled:

class Base {};
class Derived : public Base {};

std::unique_ptr<Base> MakeDerived()
    auto derived = std::make_unique<Derived>();
    return derived;

int main()
    auto base = MakeDerived();
    std::cout << "Type: " << typeid(base).name() << '\n';

Live Sample Here

Is the return value technically being move-constructed in this case, due to copy elision? And if so, does that mean unique_ptr's move constructor is designed to support implicit upcasts of user class types? It's hard to tell from the cppreference documentation (#6), unless I'm supposed to assume on that documentation page that template type U is distinctly different from type T for the class itself.

The one thing that makes it obvious this works is that unique_ptr is not copy constructible, and since I don't convert it to an rvalue via std::move(), there's no other explanation as to why the code would compile other than copy elision. However because it doesn't work with clang 3.8, which accepts the -std=c++14 flag, I want to make sure the standard itself guarantees that somewhere (and if so, where) and this is just a compiler bug or lack of support issue in v3.8 of Clang.

like image 734
void.pointer Avatar asked Mar 07 '23 05:03


1 Answers

Copy elision (before C++17) always requires the elided constructor to be actually accessible, so it cannot be the cause of your code working.

Note, however, that C++ (since C++11) has a rule (12.8/32) which boils down to the following: "when returning an object, then under certain circumstances, first try treating the object as an rvalue and only if that fails, treat it as the lvalue it is."

In C++11, these "certain circumstances" were "copy elision is possible," which requires the returned object and the return type to be the same type (modulo cv qualification).

In C++14, these "certain circumstances" were relaxed to "copy elision is possible, or the returned object is a local variable in the function."

So in C++11, the code fails, because std::unique_ptr<Derived> (the type of derived) is different from std::unique_ptr<Base> (the return type), and so std::move would have to be used.

In C++14, the code succeeds, because derived is a local in the function, and is thus treated as an rvalue first. This makes the constructor template you were referring to applicable:

std::unique_ptr<T> can be constructed from an rvalue of type std::unique_ptr<U> if U* can be converted to T*.

Your Clang 3.8 seems to be exhibiting the C++11 behaviour; perhaps it has not (yet/fully) implemented the relaxed aspects of C++14 in that version.

(And yes, indeed you're supposed to assume that the "template type" U is distinct from the class's template parameter T. That's why it was introduced in the first place: the constructor being described by #6 is a constructor template).

like image 64
Angew is no longer proud of SO Avatar answered Mar 15 '23 07:03

Angew is no longer proud of SO