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);
}