The idea
This is just a short idea for multiple template parameter packs on a specific use case. While there are other more generic ways to achieve this, I found a method that is easier to digest for my case.
One of my learning projects is a map-like container with infinite depth, multiple specific types of keys, and any type of value.
The need for multiple template parameter packs came when I wanted to be more specific about “any type of value”. “Any” is… any. Nothing specific, clear, or well-known. And I wanted more clarity.
My map is declared as:
1 | msd::poly_map< int , double , std::string> map; |
The template arguments are the types of keys. No types for the values because the map can hold any value. But I want to be as specific as I am for the keys. What I need is to separate the key types from the value types. I want two sets of template parameters. How could I tell them apart?
The solution
After a few minutes of diving in, the idea that popped up is to store the values exactly how I store the keys: inside a variant. The bonus for switching from any to variant is that:
Variant is not allowed to allocate additional (dynamic) memory.
I introduced an auxiliary type to represent a multiple-parameter pack. And I passed two of these to my map: one for keys, one for values.
1 2 3 4 5 6 7 8 9 | template < typename ... Types> struct types { using types_ = std::variant<Types...>; }; template < typename Keys, typename Values> struct poly_map; poly_map<types< int , double >, types< int , bool >> map; |
The full source code
Everything put together in a raw version looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | #include <cassert> #include <map> #include <variant> template < typename ... Types> struct types { using types_ = std::variant<Types...>; }; template < typename ... Types> using keys = types<Types...>; template < typename ... Types> using values = types<Types...>; template < typename Keys, typename Values> struct poly_map { std::map< typename Keys::types_, poly_map> items_; using value_types = typename Values::types_; value_types value_; template < typename T> auto & operator=(T&& v) { static_assert (std::is_constructible_v<value_types, T>, "wrong value type" ); value_ = std::forward<T>(v); return * this ; } template < typename T> auto & operator[]( const T key) { return items_[key]; } template < typename T> auto & get() const { static_assert (std::is_constructible_v<value_types, T>, "wrong value type" ); return std::get<T>(value_); } }; struct X { int v{}; }; int main() { poly_map<keys< int , double >, values< int , bool , X>> map; map[1] = true ; assert (map[1].get< bool >()); map[2] = X{1}; assert (map[2].get<X>().v == 1); //map[1] = 0.1; // does not compile because map can't hold double as value map[1][2.2] = 14; assert (map[1][2.2].get< int >() == 14); } |