Is there a way to have Heterogeneous Map In C++?

130 Views Asked by At

Is there a way to have a heterogeneous map implementation as the below with the following conditions please:

  1. The types are known at run time as well as the read keys.
  2. Almost zero overhead e.g. not boost::any that's slow for my purposes.
  3. No ugly long macros.
class special_map;

class buffer
{
public:
    ~buffer() = default;
    buffer(const special_map& map) : m_map(map) {};

    template<typename T>
    void read(const std::string& name, T& value)
    {
        map.read<T>(name, value);
        return void();
    }

private:
    special_map m_map;
};

Can I use:

  1. void* with reinterpret_cast please? How? Is it safe for the client/user?
  2. can I use boost::hana or boost::fusion.

Thanks very much? (Please plz see conditions)

1

There are 1 best solutions below

8
On

Here is an implementation that internally uses std::map (or any map implementation of your choice) for every type T that we want to be able to store. From the comments, I read that it is the case that we know what types we want to store. I would expect the overhead of this implementation to be roughly the same as the underlying map implementation, but it is not my job to test that. There are no funky pointer casts or use of std::variant et al in this implementation:

#include <map>

template <typename ... T> class special_map {};
template <> class special_map<> {};

template <typename T, typename ... Args> struct MapAccess {};

template <typename T, typename First, typename ...Rest>
struct MapAccess<T, First, Rest...> {
  typedef MapAccess<T, Rest...> Next;
  
  static T read(special_map<First, Rest...>* m, const std::string& k) {
    return Next::read(&(m->rest), k);
  }

  static void write(special_map<First, Rest...>* m, const std::string& k, const T& v) {
    Next::write(&(m->rest), k, v);
  }
};

template <typename T, typename ... Rest>
struct MapAccess<T, T, Rest...> {
  static T read(special_map<T, Rest...>* m, const std::string& k) {
    return m->storage.at(k);
  }
  
  static void write(special_map<T, Rest...>* m, const std::string& k, const T& v) {
    m->storage[k] = v;
  }
};

template <typename First, typename ... Rest>
struct special_map<First, Rest...> {
  special_map<Rest...> rest;
  std::map<std::string, First> storage;
  
  template <typename T>
  void write(const std::string& k, const T& v) {
    MapAccess<T, First, Rest...>::write(this, k, v);
  }
  
  template <typename T>
  void read(const std::string& k, T& v) {
    v = MapAccess<T, First, Rest...>::read(this, k);
  }
};

So to use it, you have to list all the types that it should be able to store, e.g.

special_map<float, int, std::string> myMap;

If you try to use it with a type not listed in that list, I would expect you will get a funny compilation error, so you will know that you have to add it there :-) For example, if I try to read a bool from myMap I get the error

main.cpp:15:22: error: ‘read’ is not a member of ‘MapAccess<bool, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::Next’ {aka ‘MapAccess<bool>’}