10

I wonder which parts of the standard specify that in the following code segment:

#include <memory>

class A { };

class B : public A { };

int main()
{
    std::unique_ptr<B> bptr = std::make_unique<B>(); // (a)
    std::unique_ptr<A> aptr = std::move(bptr);       // (b)
    std::unique_ptr<A> &aptr_r = bptr;               // (c)
    std::unique_ptr<A> &&aptr_rr = std::move(bptr);  // (d)
    return 0;
}

(d) compiles and (c) does not. Please include the relevant parts of the standard in your answer or refer to them appropriately. Just for reference, Ubuntu clang version 3.6.2-1 (tags/RELEASE_362/final) (based on LLVM 3.6.2) gives me

error: non-const lvalue reference to type 'unique_ptr<A>' cannot
       bind to a value of unrelated type 'unique_ptr<B>'
       std::unique_ptr<A> &aptr_r = bptr;
                           ^        ~~~~

and gcc (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010 gives me

error: invalid initialization of reference of type ‘std::unique_ptr<A>&’
       from expression of type ‘std::unique_ptr<B>’
       std::unique_ptr<A> &aptr_r = bptr;
                                    ^

Edit:

To make my question more clear, let me add

class C { };

std::unique_ptr<C> cptr = std::make_unique<C>(); // (e)
std::unique_ptr<A> &&aptr_rr2 = std::move(cptr); // (f)

What is keeping (f) from compiling when (d) does? Obviously A and C are unrelated, but where is that detected when the std::unique_ptr constructor used to construct the temporary for both (d) and (f) is

template<class U, class E>
unique_ptr(unique_ptr<U, E> &&u);
apriori
  • 1,260
  • 11
  • 29
  • 2
    [Not really `unique_ptr`-specific.](http://coliru.stacked-crooked.com/a/7e47cee922d95250) – chris Jan 22 '16 at 16:18
  • 4
    `(c)` doesn't compile because `aptr_r` and `bptr` are of different types, and thus `aptr_` cannot be a become reference of the `bptr`. It is like this : `int x = 0; float & y = x;`. But `(d)` compiles because a temporary object of the target type, is created out of the expression `std::move(bptr)`.. and the temporary object binds to the rvalue reference. Note that once you create this rvalue-reference, `bptr` becomes null, as it has been moved. – Nawaz Jan 22 '16 at 16:23

1 Answers1

4

The key difference between your cases lies in the fact that rvalue references can bind indirectly (via a temporary), while non-const lvalue references cannot. In both (c) and (d), the initializer is not similar or convertible to the type referred to as determined in [dcl.init.ref]/(5.1), therefore [dcl.init.ref]/(5.2) must apply - immediately ruling out (c):

Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference.

Note also that unique_ptr<A> and unique_ptr<B> are distinct, unrelated types, regardless of how A and B are related.

You can observe that rule with scalars, too:

int&& i = 0.f; // Ok
int& i = 0.f; // Not ok
Columbo
  • 60,038
  • 8
  • 155
  • 203