C++ multiple template parameter packs

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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.