A type that can hold both Foo* and std::shared_ptr<Foo>

Viewed 204

What's the most convenient way to be able to store either std::shared_ptr or Foo* in the same type?

Foo* a = ...;
std::shared_ptr<Foo> b = ...;

Bar c = a; // Success, Bar type can hold Foo*
Bar d = b; // Success, Bar type can also hold std::shared_ptr<Foo>

std::variant<Foo*, std::shared_ptr< Foo>> is okay, but it's not possible to dereference it directly and that is kind of annoying. Is there a better way?

5 Answers

Just use a std::shared_ptr<Foo>.

While it is rarely useful, you can in fact construct a non-owning non-counting std::shared_ptr:

auto p = std::shared_ptr<Foo>{std::shared_ptr<void>(), raw_pointer};

If you want to cater to weird people disrespecting the abstraction (looking at the reference-counts, to be specific), you could also stash an eternal anchor somewhere and use that:

struct pass_t {
    template <class... T>
    constexpr int operator()(T&&...) noexcept
    { return 0; }
};
constexpr inline pass_t pass;

inline const std::shared_ptr<void> anchor {nullptr, pass};

auto p = std::shared_ptr<Foo>{anchor, raw_pointer};

You can just wrap std::variant<Foo*, std::shared_ptr< Foo>> in something that does the right dereferencing

class Bar
{
public:
    explicit Bar(Foo* foo) : foo(foo) {}
    explicit Bar(std::shared_ptr<Foo> foo) : foo(foo) {}

    Foo& operator*() const { return std::visit(deref, foo); }
    Foo* operator->() const { return std::visit(arrow, foo); }

private:
    struct {
        Foo& operator()(Foo* f) { return *f; }
        Foo& operator()(std::shared_ptr<Foo> f) { return *f; }
    } deref;

    struct {
        Foo* operator()(Foo* f) { return f; }
        Foo* operator()(std::shared_ptr<Foo> f) { return f.get(); }
    } arrow;

    std::variant<Foo*, std::shared_ptr<Foo>> foo;
};

Basically the same as Caleth's, but more generic and exposing the variant nature of that new type:

template <class... PointerTypes>
struct PointerHolder : std::variant<PointerTypes...> {
    using std::variant<PointerTypes...>::variant;
    
    auto *operator -> () const {
        return &operator*();
    }
    
    auto &operator * () const {
        return std::visit([](auto const &p) -> auto & { return *p; }, *this);
    }
};

See it live on Wandbox

Use shared_ptr to hold both the unowned Foo* and the owned Foo*. You choose which is which by using a custom deleter that does nothing:

struct not_owned
{
    void
    operator()(void*) const
    {
    }
};


Foo f;
std::shared_ptr<Foo> sp{&f, not_owned{}};  // sp doesn't own the pointer
sp = std::make_shared<Foo>();              // sp does own the pointer

variant might work with some helpers.

template<class X>
struct is_variant: std::false_type{};
template<class...Ts>
struct is_variant< std::variant<Ts...> >:std::true_type{};
template<class X>
constexpr bool is_variant_v = is_variant<X>{};

template<class F>
struct variant_method {
  F f;
  template<class Variant> requires (is_variant_v<std::decay_t<Variant>>)
  friend auto operator->*( Variant && variant, variant_method const& self ) {
    return [&](auto&&...args)->decltype(auto) {
      return std::visit( [&](auto&& val)->decltype(auto) {
        return self.f( decltype(val)(val), decltype(args)(args)... );
      }, std::forward<Variant>(variant) );
    };
  }
};
template<class F>
variant_method(F&&)->variant_method<std::decay_t<F>>;

now we can do this:

constexpr variant_method dereference{ [](auto&& x)->decltype(auto) { return *x; } };
template<class T>
constexpr variant_method operator_{ [](auto&& x)->T { return static_cast<T>(x); } };
constexpr auto to_bool = operator_<bool>;

and here we go!

struct something {
  int x = 0;
};

std::variant<std::shared_ptr<something>, something*> some_ptr;

some_ptr = new something;
if ((some_ptr->*to_bool)())
  (some_ptr->*dereference)().x = 7;

some_ptr = std::make_shared<something>();

if ((some_ptr->*to_bool)())
  (some_ptr->*dereference)().x = 3;

Live example.

Now we can wrap it up pretty

template<class...Ts>
struct variant_ptr:std::variant<Ts...> {
  using base = std::variant<Ts...>;
  using base::base;

  auto& operator*() const { return (((base const&)*this)->*dereference)(); }
  auto& operator*() { return (((base&)*this)->*dereference)(); }

  auto* get() const { return std::addressof(**this); }
  auto* get() { return std::addressof(**this); }

  auto* operator->() const { return get(); }
  auto* operator->() { return get(); }

  explicit operator bool() const { return (((base const&)*this)->*to_bool)(); }
};

now we do this:

variant_ptr<Foo*, std::shared_ptr<Foo>>

and everything "just works".

Live example.

Of course we can skip all of the variant_method stuff above and just implement std::visit in our variant_ptr.

Oh, and here is a version that gets rid of those base& casts by teaching variant_method::operator->* all about interacting with derived-from-variant types.

template<class...Ts>
constexpr decltype(auto) get_variant_base(std::variant<Ts...> const& x) { return x; }
template<class...Ts>
constexpr decltype(auto) get_variant_base(std::variant<Ts...>& x) { return x; }
template<class...Ts>
constexpr decltype(auto) get_variant_base(std::variant<Ts...>&& x) { return std::move(x); }

constexpr auto get_variant_base_lambda = [](auto&&x)->decltype(get_variant_base(decltype(x)(x))){ return get_variant_base(decltype(x)(x)); };

template<class X>
constexpr bool is_variant_derived_v = std::is_invocable_v< decltype(get_variant_base_lambda), X >;

template<class F>
struct variant_method {
  F f;
  template<class Variant> requires (is_variant_derived_v<Variant>)
  friend auto operator->*( Variant && variant, variant_method const& self ) {
    return [&](auto&&...args)->decltype(auto) {
      return std::visit( [&](auto&& val)->decltype(auto) {
        return self.f( decltype(val)(val), decltype(args)(args)... );
      }, get_variant_base(std::forward<Variant>(variant)) );
    };
  }
};

template<class...Ts>
struct variant_ptr:std::variant<Ts...> {
  using base = std::variant<Ts...>;
  using base::base;

  auto& operator*() const { return ((*this)->*dereference)(); }
  auto& operator*() { return ((*this)->*dereference)(); }

  auto* get() const { return std::addressof(**this); }
  auto* get() { return std::addressof(**this); }

  auto* operator->() const { return get(); }
  auto* operator->() { return get(); }

  explicit operator bool() const { return ((*this)->*to_bool)(); }
};
Related