2

I am trying to do something that I thought should initially be trivial, but am running into design problems on how I should ideally structure this. I want to create a map of key-value pairs that stores a bunch of parameters. The "key" in here is always a string. However the value could be an int, double, string or char. I have enums to define the type:

typedef enum {INT = 0, DOUBLE, STRING, CHAR} val_type;

I was thinking of structuring it as a vector of maps

std::vector<std::map<std::string, T>> params;

What is the most principled way to go about this?

EDIT: Updated the vector declaration.

Ammar Husain
  • 1,789
  • 2
  • 12
  • 26
  • 2
    What is the `int` in `vector`'s template arguments supposed to be? You need a `vector` of [Boost.Variants](http://www.boost.org/doc/libs/release/doc/html/variant.html). – Praetorian Jun 05 '15 at 23:30
  • Oops, I made a mistake there! It should just be `std::vector> params;` – Ammar Husain Jun 05 '15 at 23:38
  • So, would a `boost::variant` solve your problem? You could also write your own type-safe tagged union. Another question is, how do you intend to use this vector of maps to `T`? – Yakk - Adam Nevraumont Jun 06 '15 at 00:31
  • Why do you need to do this? What problem are you trying to solve? – Galik Jun 06 '15 at 00:36
  • Unfortunately, boost is not encouraged at my workplace. I am basically trying to create a container to store parameters of several types. Each parameter would have a string name (key) and an associated value. However the value could either be an int, double, string etc. I was planning to use enums for types and then create a vector of hashmaps. Any ideas? – Ammar Husain Jun 06 '15 at 06:24
  • @AmmarHusain If Boost is ruled out, are you in fact restricted to at most C++11, and what is your compiler? If you can stretch to GCC 5.x with `-std=c++14` then you could `#include `, and employ an `std::map>`, if an `std::experimental` library is tolerable. – Mike Kinghan Jun 06 '15 at 10:48
  • @AmmarHusain Is Boost *discouraged* or *disallowed*? If it's the former, I'd say use it to solve this problem, it's by far the most convenient solution. If you're not allowed to, you need to [create and manage a tagged union](https://stackoverflow.com/a/30493617/241631), and use a `vector` – Praetorian Jun 06 '15 at 19:19

1 Answers1

2

I've got 15 minutes. Incoming tagged union. Not as good as boost::variant, and I might not actually compile it, but it should get you started.

template<size_t S>
using size = std::integral_constant<std::size_t, S>;
template<bool b>
using bool_t = std::integral_constant<bool, b>;

template<size_t...Ss>
struct max_size:size<0>{};
template<size_t S0, size_t...Ss>
struct max_size:size<(std::max)(S0, max_size<Ss...>{}())>{};

template<class...Ts>
struct max_alignof : max_size< alignof(Ts)... >{};
template<class...Ts>
struct max_sizeof : max_size< sizeof(Ts)... >{};


template<class X>struct tag{using type=X;};
template<class...>struct types{using type=types;};
template<class Tag>using type_t=typename Tag::type;

template<class T, class Types>
struct index_of {};
template<class T, class...Ts>
struct index_of<T, types<T,Ts...>>:size<0>{};
template<class T, class T0, class...Ts>
struct index_of<T, types<T0,Ts...>>:size<
  index_of<T, types<Ts...>>{}+1
>{};

template<class X>
struct emplace_as {};

template<class F, class...Ts>
void invoke( types<Ts...>, void* p, F&& f, size_t i ) {
  auto* pf = std::addressof(f);
  using operation=void(*)(decltype(pf), void*);
  operation table[]={
    +[](decltype(pf), void* p){
      Ts* pt = static_cast<Ts*>(p);
      std::forward<F>(*pf)( *pt );
    }...
  };
  table[i]( pf, p );
}

template<class T0, class...Ts>
struct one_of {
  std::aligned_storage< max_sizeof<T0, Ts...>{}, max_alignof<T0, Ts...>{} > data;
  size_t index = -1;
  using my_types = types<T0, Ts...>;
  template<class T>
  using sfinae_my_type = tag< size<index_of<X,my_types>{}> >;

  one_of():one_of(emplace_as<T0>{}) {}

  // brace construction support for only the first type:
  one_of(T0&&t0):one_of(emplace_as<T0>{}, std::move(t0)) {}

  template<class X, class...Args, class=sfinae_my_type<X>>
  one_of(emplace_as<X>, Args&&... args){
    emplace( emplace_as<X>{}, std::forward<Args>(args)... );
  }
  template<class X, class=sfinae_my_type<std::decay_t<X>>>
  one_of(X&& x) {
    emplace_as(std::forward<X>(x));
  }
  template<class X, class=sfinae_my_type<X>>
  X* get() {
    if (index_of<X, my_types>{}==index) {
      return static_cast<X*>(&data);
    } else {
      return nullptr;
    }
  }
  template<class X, class=sfinae_my_type<X>>
  X const* get() const {
    if (index_of<X, my_types>{}==index) {
      return static_cast<X const*>(&data);
    } else {
      return nullptr;
    }
  }

  template<class X, class=sfinae_my_type<std::decay_t<X>>>
  void emplace(X&& x) {
    emplace_as<std::decay_t<X>>{}, std::forward<X>(x));
  }

  template<class X, class...Args, class=sfinae_my_type<X>>
  void emplace( emplace_as<X>, Args&&...args ) {
    destroy();
    new(&data) X(std::forward<Args>(args)...);
    index = index_of<X, list<T0, Ts...>>{};
  }

  template<class F>
  void my_invoke(F&& f) {
    my_invoke( std::forward<F>(f), index );
  }
  template<class F>
  void apply(F&& f) {
    invoke( my_types{}, &data, std::forward<F>(f), index );
  }
  void destroy() {
    if (index != -1) {
      apply([&](auto&& x){
        using X=std::decay_t<decltype(x)>;
        index = -1;
        x.~X();
      });
    };
  }

  one_of& operator=(one_of const& rhs){
    if (this == &rhs) return *this;
    destroy();
    rhs.apply( [&](auto const& x) {
      using X=std::decay_t<decltype(x)>;
      emplace( emplace_as<X>{}, decltype(x)(x) );
    } );
    return *this;
  }
  one_of& operator=(one_of&& rhs){
    if (this == &rhs) return *this;
    destroy();
    rhs.apply( [&](auto & x) {
      using X=std::decay_t<decltype(x)>;
      emplace( emplace_as<X>{}, std::move(x) );
    } );
    return *this;
  }
  ~one_of(){destroy();}
};

which is a quick sketch of a tagged union type.

Store a one_of<int, double, std::string, char> as your T. Access via either apply passing in a function that has overrides for each, or get<T>.

The above is C++14, because it makes it easier. It doesn't include an apply that has a return value, again because it makes it easier. Both can be remedied, but it takes work, and really, you should look at how boost does it rather than use the above.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524