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:
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.
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:
#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); }