47

Using C++11, let's say I have factory functions dealing with base and derived classes:

#include <memory>

using namespace std;

struct B { virtual ~B() {} };
struct D : B {};

unique_ptr<B> MakeB()
{
    auto b = unique_ptr<B>( new B() );
    return b; // Ok!
}

unique_ptr<B> MakeD()
{
    auto d = unique_ptr<D>( new D() );
    return d; // Doh!
}

On the last line above, I need move(d) in order to make it work, otherwise I get "Error: invalid conversion from std::unique_ptr<D> to std::unique_ptr<D>&&." My intuition said that in this context, the compiler should know that it could implicitly make d an rvalue and move it into the base pointer, but it doesn't.

Is this a non-conformancy in my compilers (gcc 4.8.1 and VS2012)? The intended design of unique_ptr? A defect in the standard?


Update: C++14 fixes this. Newer compilers such as GCC 9 accept the original code even with -std=c++11.

metal
  • 6,202
  • 1
  • 34
  • 49
  • There is only an implicit move if the types are the same. – Simple Feb 25 '14 at 15:11
  • @Simple: Do you have a normative reference to back that claim? (If you do, it would be the correct answer here) – Jan Hudec Feb 25 '14 at 15:12
  • 3
    @JanHudec 12.8/31 and 12.8/32. Implicit move is specified in terms of the compiler failing to elide in cases where it would be allowed to, and elision is not allowed when the types are different. – Simple Feb 25 '14 at 15:14

2 Answers2

35

The compiler's behaviour is correct. There is only an implicit move when the types are the same, because implicit move is specified in terms of the compiler failing to perform copy elision in cases where it is actually allowed (see 12.8/31 and 12.8/32).

12.8/31 (copy elision):

in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type...

12.8/32 (implicit move):

When the criteria for elision of a copy operation are met, [...], overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

Simple
  • 13,992
  • 2
  • 47
  • 47
  • 24
    +1, but note that this has been changed for C++14, [DR 1579](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3833.html#1579) was approved at the last meeting. – Jonathan Wakely Feb 25 '14 at 15:21
  • Thanks, @Simple: That definitively answers the question. And thanks, @JonathanWakely: That confirms my intuition that it should work without the `move()`. – metal Feb 25 '14 at 19:07
-3

With the addition of the std::move calls in the return statements, this code works for me on Visual Studio 2013:

#include <memory>

using namespace std;

struct B { virtual ~B() {} };
struct D : B {};

unique_ptr<B> MakeB()
{
  auto b = unique_ptr<B>( new B() );
  return std::move( b );  // *** std::move() added here! ***
}

unique_ptr<B> MakeD()
{
  auto d = unique_ptr<D>( new D() );
  return std::move( d );  // *** std::move() added here! ***
}

The following also works

unique_ptr<B> MakeB()
{
  return unique_ptr<B>( new B() );  // *** Returning a temporary! ***
}

unique_ptr<B> MakeD()
{
  return unique_ptr<D>( new D() );  // *** Returning a temporary! ***
}

Of course, if you're returning std::unique_ptr<B> anyway, why not shove the D instance into a std::unique_ptr<B> in the first place? Then there's no conversion necessary at all!

aldo
  • 2,927
  • 21
  • 36
  • 2
    Well, that's even stated in question. The real question is *why* a `std::move()` is necessary here. – Angew is no longer proud of SO Feb 25 '14 at 15:15
  • Sorry. Totally missed that. Oh well. (But hey, it still works in VS2013! Hurray! :-P ) – aldo Feb 25 '14 at 15:22
  • @pepper_chico But the accepted answer says it **is** conformant! – aldo Feb 25 '14 at 16:30
  • @aldo the accepted answer says the OP compiler error report is correct behavior, which means, the OP's code **is not** conformant to standard. what you're saying, it seems, is that allowing the return without errors is right, which currently is not. It's not clear whether you're saying VS2013 is compiling the OP's code or not, it looks like you're Harraying! because it it's. – oblitum Feb 25 '14 at 16:40
  • @pepper_chico I edited my answer to contain code comments emphasizing that I had *added* the `std::move` calls (as suggested by the OP) which make the code standard-compliant (which - surprise! - compiles with no error). – aldo Feb 25 '14 at 19:47