Search code examples
c++inheritancecastingc++17shared-ptr

Casting shared_ptr in initializer list


Is it possible to up-cast a shared pointer object reference in an initializer list?

I have two class hierarchies with matching derived classes. Let's say I have a Base class which is derived to a Foo and a Bar class. Then I have a separate hierarchy with a ProxyBase class and then (to match the other hierarchy) there's FooProxy and BarProxy.

The ProxyBase class stores a shared_ptr<Base>. When a ProxyFoo is created it requires a std::shared_ptr<Foo>&, when a ProxyBar is created it requires a std::shared_ptr<Bar>&.

There are two ways I can make this work:

  • Don't use references to the std::shared_ptr<*> objects.
  • Use std::shared_ptr<Base>& obj as input to the ProxyFoo and ProxyBar constructors. This requires a down-cast (which I want to avoid) in the constructor (since I want the Foo and Bar specific properties), plus that it makes the contract/API more allowing than it should be.

But I'm wondering if this can be done with references.

I think I know what the problem is: When I remove the references it works, and I think that's because "casting" a shared_ptr<X> is technically not allowed; it can only be "casted" by creating a new shared_ptr object of a derived/base type. When using references one tries to actually cast one shared_ptr object to another which isn't allowed. Is this correct?

Originally I thought this was one of those cases where std::static_pointer_cast would help, but if my hypothesis is correct then that won't help either.

Self-contained example:

#include <memory>

class Base { };

class Foo : public Base
{
private:
  int val_;

public:
  Foo(int val) : val_(val) { }
};

class ProxyBase
{
protected:
  std::shared_ptr<Base> obj_;

  ProxyBase(std::shared_ptr<Base>& obj) : obj_(obj) { }
};

class FooProxy : public ProxyBase
{
public:
  FooProxy(std::shared_ptr<Foo>& fobj) : ProxyBase(fobj) { }
};

int main()
{
  auto f = std::make_shared<Foo>(42);
  auto fp = std::make_shared<FooProxy>(f);
}

So my question boils down to: Is it possible to do what I'm trying to do in the (currently non-functional) code above without removing the references and yet keep the Foo and Bar specific constructors to FooProxy and BarProxy?


Solution

  • A shared_ptr<Derived> can be converted to a shared_ptr<Base>, but it is not a shared_ptr<Base>.

    In other words, this compiles:

    std::shared_ptr<Derived> pd;
    std::shared_ptr<Base> pb = pd;
    

    But this does not:

    std::shared_ptr<Base>& pb = pd;
    

    But this does, because const references can bind to temporaries and this will implicitly perform a conversion:

    std::shared_ptr<Base> const& pb = pd;
    

    The problem is that you're taking non-const lvalue references in your constructors. You should either take const lvalue references or just take them by value.