tags:

views:

227

answers:

2

I'm currently trying to use Howard Hinnant's unique_ptr implementation, and am running into a compile error. Here is some sample code:

struct Base {};

struct Derived : public Base {};

void testfun(boost::unique_ptr<Base>);

void test()
{
    unique_ptr<Derived> testDerived; 
    unique_ptr<Base> testBase(move(testDerived)); // ok, construct base explicitly from derived 
    testfun(move(testBase));                      // ok, pass base to testfun which expects base 
    testfun(unique_ptr<Base>(move(testDerived))); // ok, explicitly converts to unique_ptr<Base>
    testfun(move(testDerived));                   // error on this line
}

The error I get is

In function 'void test()':
error: no matching function for call to 'boost::unique_ptr<Base, boost::default_delete<Base> >::unique_ptr(boost::unique_ptr<Base, boost::default_delete<Base> >)'
note: candidates are: boost::unique_ptr<T, D>::unique_ptr(boost::detail_unique_ptr::rv<boost::unique_ptr<T, D> >) [with T = Base, D = boost::default_delete<Base>]
note:                 boost::unique_ptr<T, D>::unique_ptr(boost::unique_ptr<T, D>&) [with T = Base, D = boost::default_delete<Base>]
error:   initializing argument 1 of 'void testfun(boost::unique_ptr<Base, boost::default_delete<Base> >)' from result of 'boost::unique_ptr<T, D>::unique_ptr(boost::unique_ptr<U, E>, typename boost::enable_if_c<((((! boost::is_array<U>::value) && boost::detail_unique_ptr::is_convertible<typename boost::unique_ptr<U, boost::default_delete<U> >::pointer,typename boost::detail_unique_ptr::pointer_type<T, D>::type>::value) && boost::detail_unique_ptr::is_convertible<E,D>::value) && ((! boost::is_reference<D>::value) || boost::is_same<D,E>::value)), void>::type*) [with U = Derived, E = boost::default_delete<Derived>, T = Base, D = boost::default_delete<Base>]'

It seems like the offending line should not fail. Is this a bug in the implementation, a limitation of the implementation due to the lack of C++0x language features, or a misunderstanding of the rules of unique_ptrs?

(Note, I know this won't work at run-time because I'm moving the same thing more than once; I'm just trying to figure out the compile-time error.)

+1  A: 

For a similar example, see this which should fail too

unique_ptr<Base> testBase = move(testDerived);

The problem here is how the move semantic is implemented: The "copy constructor" takes a non-const reference, thus not being able to bind to temporaries. To still "move" from temporaries, the class has a conversion function (following are really just conceptual - they may be differently implemented in detail):

operator rv<T>() { return rv<T>(*this); }

And a constructor will take that object:

unique_ptr(rv<T> r):ptr_(r.release()) { }

Here is an example that fails for the same reason:

// move helper. rv<> in unique_ptr
struct E { };

// simulates a unique_ptr<D>
struct D { };

// simulates the unique_ptr<B>
struct A {
  A() { }

  // accepts "derived" classes. Note that for unique_ptr, this will need that
  // the argument needs to be copied (we have a by-value parameter). Thus we 
  // automatically ensure only rvalue derived-class pointers are accepted.
  A(D) { } 

  // these will accept rvalues
  A(E) { }
  operator E() { return E(); }

private:
  A(A&); // private, error if passed lvalue
};

Now, consider this code:

// allowed: goes: D -> A(D)
A a((D()));

// compile failure. Goes:
// D -> A(D) -> A(E)
A a = D();

Copy initialization will first convert to A. But then, the temporary A object is tried to be copied again to the final object. This will need the way using operator E. But that's another user defined conversion in the initialization, which the standard forbids:

13.3.3.1/4

When invoked for the copying of the temporary in the second step of a class copy-initialization, [...], only standard conversion sequences and ellipsis conversion sequences are allowed.

This is why your code fails.

Johannes Schaub - litb
A: 

Further research has lead me to this note, leading me to believe that this is a known limitation of the implementation:

3 of the tests currently fail for me (fail at compile time, supposed to compile, run and pass). These are all associated with the converting constructor specified in [unique.ptr.single.ctor]. When the source and target are of different type, this emulation demands that the conversion be explicit, and refuses to compile on implicit conversions:

unique_ptr<base> b(unique_ptr<derived>()); // ok

unique_ptr<base> b = unique_ptr<derived>(); // causes 3 compile time failures under unique.ptr/unique.ptr.single/unique.ptr.single.ctor .
SCFrench
litb's writeup is very informative, but I think this one is a more direct answer to the question. Both should be read by interested parties :-)
SCFrench