Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload method for unique_ptr and shared_ptr is ambiguous with polymorphism

Tags:

Coding stuff after taking the hint from my previous question's answer, I ran into an issue with overloading Scene::addObject.

To reiterate the relevant bits and make this self contained, with the least details possible:

  • I have a hierarchy of objects inheriting from Interface of which there are Foos and Bars;
  • I have a Scene which owns these objects;
  • Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
  • the main passes them to the Scene instance, which takes ownership.

Minimal code example is this:

#include <memory> #include <utility>  class Interface { public:   virtual ~Interface() = 0; };  inline Interface::~Interface() {}  class Foo : public Interface { };  class Bar : public Interface { };  class Scene { public:   void addObject(std::unique_ptr<Interface> obj); //  void addObject(std::shared_ptr<Interface> obj); };  void Scene::addObject(std::unique_ptr<Interface> obj) { }  //void Scene::addObject(std::shared_ptr<Interface> obj) //{ //}  int main(int argc, char** argv) {   auto scn = std::make_unique<Scene>();    auto foo = std::make_unique<Foo>();   scn->addObject(std::move(foo));  //  auto bar = std::make_shared<Bar>(); //  scn->addObject(bar); } 

Uncommenting the commented lines results in:

error: call of overloaded 'addObject(std::remove_reference<std::unique_ptr<Foo, std::default_delete<Foo> >&>::type)' is ambiguous     scn->addObject(std::move(foo));                                  ^  main.cpp:27:6: note: candidate: 'void Scene::addObject(std::unique_ptr<Interface>)'   void Scene::addObject(std::unique_ptr<Interface> obj)        ^~~~~  main.cpp:31:6: note: candidate: 'void Scene::addObject(std::shared_ptr<Interface>)'   void Scene::addObject(std::shared_ptr<Interface> obj)        ^~~~~ 

Uncommenting the shared and commenting the unique stuff also compiles, so I take it the problem is, like the compiler says, in the overload. However I need the overload as both these types will need to be stored in some kind of collection, and they are indeed kept as pointers to base (possibly all moved into shared_ptrs).

I'm passing both by-value because I want to make clear I'm taking ownership in Scene (and upping the reference counter for the shared_ptrs). Not really clear to me where the issue lies at all, and I couldn't find any example of this elsewhere.

like image 296
aPonza Avatar asked Nov 26 '18 11:11

aPonza


People also ask

In what situation is a shared_ptr more appropriate than a unique_ptr?

Use unique_ptr when you want a single pointer to an object that will be reclaimed when that single pointer is destroyed. Use shared_ptr when you want multiple pointers to the same resource.

What is the difference between shared PTR and unique_ptr?

Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.

Is unique_ptr an example of Raii idiom?

yes, it's an RAII class.


2 Answers

The problem you are encountering is this constructor of shared_ptr (13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr (6) (also not explicit).

template< class Y, class Deleter >  shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13) 

13) Constructs a shared_ptr which manages the object currently managed by r. The deleter associated with r is stored for future deletion of the managed object. r manages no object after the call.

This overload doesn't participate in overload resolution if std::unique_ptr<Y, Deleter>::pointer is not compatible with T*. If r.get() is a null pointer, this overload is equivalent to the default constructor (1). (since C++17)

template< class U, class E > unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6) 

6) Constructs a unique_ptr by transferring ownership from u to *this, where u is constructed with a specified deleter (E).

This constructor only participates in overload resolution if all of the following is true:

a) unique_ptr<U, E>::pointer is implicitly convertible to pointer

b) U is not an array type

c) Either Deleter is a reference type and E is the same type as D, or Deleter is not a reference type and E is implicitly convertible to D

In the non polymorphic case, you are constructing a unique_ptr<T> from a unique_ptr<T>&&, which uses the non-template move constructor. There overload resolution prefers the non-template


I'm going to assume that Scene stores shared_ptr<Interface>s. In that case you don't need to overload addObject for unique_ptr, you can just allow the implicit conversion in the call.

like image 82
Caleth Avatar answered Oct 20 '22 21:10

Caleth


The other answer explains the ambiguity and a possible solution. Here's another way in case you end up needing both overloads; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching. The boiler-plate code is hidden in private part of Scene:

class Scene {     struct unique_tag {};     struct shared_tag {};     template<typename T> struct tag_trait;     // Partial specializations are allowed in class scope!     template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };     template<typename T>             struct tag_trait<std::shared_ptr<T>>   { using tag = shared_tag; };    void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);   void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);  public:     template<typename T>     void addObject(T&& obj)     {         addObject_internal(std::forward<T>(obj),             typename tag_trait<std::remove_reference_t<T>>::tag{});     } }; 

Full compilable example is here.

like image 29
jrok Avatar answered Oct 20 '22 20:10

jrok