Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moving a shared_ptr<StructA> into a shared_ptr<variant<StructA, StructB>>

Tags:

c++

c++17

c++14

I'm trying to avoid a rather complex inheritance chain for a set of plain old data structs, but I need to store them all in a vector + share ownership with the caller.

Struct A {};
Struct B {};

using TInstruction = std::variant<A, B>;

struct holder {
  std::vector<std::shared_ptr<TInstruction>> items;
};

static holder h;

// Every individual TInstruction gets its own add function, for validation purposes
void add(std::shared_ptr<A> a) {
  // Somehow add this to h, while still sharing ownership with the caller
  // h.emplace_back(???)
}


int main() {
  holder h;

  auto a = std::make_unique<A>();
  auto b = std::make_unique<B>();

  add(a);
  // add(b) // not implemented
}

I've had moderate (but annoyingly poor) success with the following changes to the original idea:

  1. Use shared pointers inside the variant, i.e. using TInstruction = std::variant<std::shared_ptr<A>, std::shared_ptr<B>>
  2. Accepting a std::weak_ptr in add() and use .lock() to turn it into a std::shared_ptr

I don't mind #2 (it seems to me that it might be the right way to do it), but keeping the shared_ptr inside the variant inside of "outside" of it leads to some very verbose code and pattern matching.

Is it possible to do this at all? I essentially want to change the type of the shared pointer, but still express the idea of shared ownership.

like image 259
Emil Ahlbäck Avatar asked Nov 06 '22 06:11

Emil Ahlbäck


1 Answers

Instead of using variant, you could take advantage of shared_ptr<void> being able to hold a shared_ptr to anything, as long as you keep track of the type it is holding yourself, as in:

// Generic typelist
template <typename...>
struct Types;

// Find the 0-based index of type T in Types<...>
template <typename, typename>
struct Index;

// T is the first type in Types<...>
template <typename T, typename... Us> 
struct Index<T, Types<T, Us...>> : std::integral_constant<int, 0> {}; 

// T is not the first type in Types<...>
template <typename T, typename U, typename... Us> 
struct Index<T, Types<U, Us...>>
    : std::integral_constant<int, 1 + Index<T, Types<Us...>>()> {}; 

template <typename... Ts> 
struct SharedPtrVariant {
    template <typename T>
    explicit SharedPtrVariant(std::shared_ptr<T> p)
        : sp(std::move(p)), index(Index<T, Types<Ts...>>()) {}

    template <typename T>
    std::shared_ptr<T> get() const {
        return std::static_pointer_cast<T>(
            Index<T, Types<Ts...>>() == index ? sp : nullptr);
    }   

   private:
    std::shared_ptr<void> sp; 
    int index;
};
like image 179
Nevin Avatar answered Nov 15 '22 13:11

Nevin