Vlad Didenko
Vlad Didenko

Reputation: 4771

How to store templated heterogeneous objects in an STL container

The question is about a code developed in MS Visual C++ 11, with access only to STL, no Boost.

There is a wrapper template class, roughly with this header:

template <typename Payload>
class Wrapper {
  Payload p;
  std::string src;
  Wrapper( std::string, Payload );

  Payload get();      // returns payload
  void set(Payload);  // replaces payload
  void operator ()(); // uses payload
}

Payload may be anything - pointer, int, even heavy object.

Later, Wrappers need to go in a container, like std::vector - but regardless of their specific parameter type. And that gives me trouble because container needs homogeneous elements.

I have tried the base class suggestions like this from KennyTM, however it gives me some issues with methods get() and set() - those need cast (?) when used from a vector because elements look like a base class if used in the pattern suggested by that answer.

Upvotes: 2

Views: 1199

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275230

template<typename Payload>
struct Wrapper;

struct WrapperBase {
  std::string src;
  WrapperBase( std::string s ):src(s) {}
  template<typename Payload>
  Payload get() const;
  template<typename Payload>
  void set(Payload);
  virtual void operator ()() = 0; // uses payload
};

template <typename Payload>
struct Wrapper {
  Payload payload;
  Wrapper( std::string s, Payload p ):WrapperBase(s),payload(p) {}

  Payload get() const { return payload; };      // returns payload
  void set(Payload p) { payload = p; };  // replaces payload
  virtual void operator()() override; // todo
}

template<typename Payload>
Payload WrapperBase::get() const {
  Assert(dynamic_cast<Wrapper<Payload> const*>(this));
  return static_cast<Wrapper<Payload> const*>(this)->get();
}
template<typename Payload>
void WrapperBase::set(Payload p) {
  Assert(dynamic_cast<Wrapper<Payload>*>(this));
  static_cast<Wrapper<Payload>*>(this)->set(p);
}

Users of a WrapperBase, if they want to set/get the payload, need to know what the type of the payload is. You can use dynamic_cast<Wrapper<Payload>*> to figure out if a given WrapperBase is a specific kind if you don't know.

This doesn't have value semantics, so you'll want to store a vector of smart pointers to WrapperBase rather than actual instances. std::shared_ptr or std::unique_ptr are good candidates with very different behavior.

If there is a finite bounded set of Payloads, a visitor pattern can work.

If you need value semantics, a pImpl pattern that stores the actual payload can do that with manual operator= that clones the pImpl.

Asking "do you hold type T" is possible, but generally is similar to a dynamic_cast.

Upvotes: 3

For that you will need to use some sort of type erasure. From the most basic (provide a base type, store the elements by pointer to the base) to fancier solutions like boost::any you get to choose (I know you mentioned no boost, but you can always take a look at the implementation). Alternatively you could use a variant approach (similar to boost::variant) if the set of payloads is known and relatively small, but that might be harder to implement for a single use.

Upvotes: 5

Related Questions