Reputation: 1849
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.
Upvotes: 2
Views: 112
Reputation: 275340
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.
Upvotes: 2