-1

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)

Vero
  • 313
  • 2
  • 9
  • Is the list of all types used known at compile time? – Quimby Nov 28 '22 at 18:12
  • Yes and No. Yes becasue at the time being it's a finite set. But I want to avoid everytime I want to add a type I have to change the code? Thanks for checking this a lot! – Vero Nov 28 '22 at 18:13
  • 9
    `boost::any` or `std::any` _are_ the safe versions of `void*`. – Miles Budnek Nov 28 '22 at 18:14
  • 5
    In that case you could use `std::variant` and ordinary `std::map`, then `read` is possible, perhaps returning `std::optional`. Yes, that would require listing the types and any new ones. If the list is not know, you will unlikely do better than `std::any`. – Quimby Nov 28 '22 at 18:16
  • MilesBudnek and Quimby thanks so much. did this conversation finish? Are you 100% I shouldn't waste time looking please? – Vero Nov 28 '22 at 18:19
  • In a `std::map`, the first element must be sortable. My suggestion is to create a parent class `sortable` that contains virtual function for ordering. You can then have various derivation of `sortable`. Use pointers to store in the map. Don't forget that the classes must be copyable. – Thomas Matthews Nov 28 '22 at 18:21
  • 4
    To me it seems your actual problem is that `boost::any` is slow, so maybe look into why it appears to be slow? I slightly doubt you can do any better though, type erasure is simply not free. Also are you sure, that is the code, have you measured? – Quimby Nov 28 '22 at 18:28
  • @Thomas Matthews I fail to understand? The first argument you can assume as in the code it's a string. What is the T? Can I know the T and call it's member functions with this? If you mean to say sortable is the second argument, I can only read the the sortable class, I don't what member functions it has? – Vero Nov 28 '22 at 18:31
  • @Quimby clear, thanks very much! I know C++ is strong type language but it appears that this issue should be popular, like for example in event systems, you have to always cast so you can dispatch the event. Likely I will just use boost::any or std::any. – Vero Nov 28 '22 at 18:32
  • "The types are known at run time" how that ? The types must be known at compile time. The quesiton is rather: Is it a limited set of types, then you want `std::variant` or `boost::variant`. Only if the set of types is unbound you want `std::any` or `boost::any`. And then you need to pay for what you get, so I wouldnt expect that writign your own `any` will be any faster than the already existing one – 463035818_is_not_an_ai Nov 28 '22 at 18:35
  • "The types are known at run time" All I want to say is you don't have a finite number of types so I exclude the variant and macros. – Vero Nov 28 '22 at 18:38
  • Create an interface base `class types_base { ... };`, and then a `std::map< std::string, std::unique_ptr< types_base >> my_types_map;` Then add pure virtual function(s) to `types_base` and when you have a new type just inherit from it and implement the necessary virtual functions. – Richard Critten Nov 28 '22 at 18:50
  • Are you talking a `std::map` where the first element is common, but the second element has variable type? – Thomas Matthews Nov 28 '22 at 21:27

1 Answers1

1

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>’}
Rulle
  • 4,496
  • 1
  • 15
  • 21
  • I am reading the code, but it looks perfect to me so far. Thanks so much. Can you think of any downsides please so I am aware? I should benchmark this again boost::any :) – Vero Nov 28 '22 at 19:03
  • @Vero The downside of this code that it is probably *more code* than for example a solution based on `boost::any`. And more code means more bugs... If you decide to use it, you may want to tweak it, for example to mark `read` as `const`. Maybe provide extra methods such as `size_t size() const` etc. You can see how `read` and `write` are implemented and then implement `size` etc using recursion with the `MapAccess` struct the same way. – Rulle Nov 28 '22 at 19:11
  • 1
    thanks so much! Running some benchmarks. Thanks a lot again!!! – Vero Nov 28 '22 at 19:23
  • I am trying to build this: special_map map; map.write(std::string("Hello"), 4.0); map.write(std::string("Bb"), 1.0); but it fails with "error C2039: 'write': is not a member of 'space::MapAccess'" – Vero Nov 28 '22 at 20:57
  • @Vero It fails because you are trying to write a `double` to a map that *does not* contain `double`, but only the types `float`, `int` and `std::string`. Add `double` to that list (`special_map map`) and try again. – Rulle Nov 28 '22 at 21:02
  • Ah missed that one ;p sorry! Thank you!! One more thing I have to do I think is also make sure the template values are unique, otherwise the recusive search might stop at one of the maps! – Vero Nov 28 '22 at 21:33
  • The recursive search will stop at the *first* matching type so having the same type occurring multiple times won't be a problem. – Rulle Nov 28 '22 at 21:57
  • Aaah that's true because the write and the read follow the same path, thanks a lot! – Vero Nov 28 '22 at 22:16