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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With