How to expand a non value paramter pack

I've been trying to expand a non value parameter pack recently in C++. Is this possible? And if it's not, why?

I mean, as you can see, in the line with the comment //, given a parameter pack for the TypeMap class, how can I call addType<T>() with each type of the parameter pack? Thanks in advance!

template <typename... T>
class TypeMap
{
    using vari_cmps = std::variant<T*...>;

    private:
        template<typename Type>
        void addType()
        {
            typemap[typeid(Type).name()] = std::make_unique<Type>(0).get();
        }

    public:
        std::map<const char*, vari_cmps> typemap{};

        TypeMap()
        {
            (addType<T,...>()); // Idk how to use this in order to make it work
        }

        ~TypeMap()
        {
            typemap.clear();
        }
};

As @HolyBlackCat has already answered in the comments, you can expand it like this:

TypeMap() {
    (addType<T>(), ...);
}

If T is std::string, int, float this would expand to:

TypeMap() {
    (addType<std::string>(), addType<int>(), addType<float>());
}

There are however a few more issues in this code-snippet:

1. addType()

addType() will not work as you'd expect, due to the unique_ptr deleteing your object after you put it into the map.

.get() only retrieves the pointer that the unique_ptr manages but does not transfer ownership, so the unique_ptr will still delete the pointed-to object once it gets out of scope, leaving a dangling pointer in your map.

so your addType() is roughly equivalent to:

template<typename Type>
void addType() {
    Type* tptr = new Type(0); // unique pointer constructs object
    typemap[typeid(Type).name()] = tptr; // insert pointer value of unique pointer
    delete tptr; // unique pointer destructs
}

You could fix this by releasing the unique_ptr after inserting its value into the map & then cleaning it up in the destructor:

template<typename Type>
void addType() {
    auto ptr = std::make_unique<Type>(0);
    typemap[typeid(Type).name()] = ptr.get();
    ptr.release(); // now unique_ptr won't delete the object
}

~TypeMap() {
    // cleanup all pointers
    for(auto& [name, ptrVariant] : typemap)
        std::visit([](auto ptr) { delete ptr; }, ptrVariant);
}

2. Consider using std::type_index instead of const char* as map key

std::type_info::name() returns an implementation-defined name for the given type, so you have no guarantee that you will get an unique name for a given type.

Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.

std::type_index on the other hand is build specifically for this purpose - using types as keys - and comes with all comparison operators & a std::hash specialization, so you can use it with std::map & std::unordered_map out of the box.

e.g.:

template <class... T>
class TypeMap
{
    using vari_cmps = std::variant<T*...>;

    private:
        template<typename Type>
        void addType()
        {
            typemap[std::type_index(typeid(Type))] = /* something */;
        }

    public:
        std::map<std::type_index, vari_cmps> typemap{};

        TypeMap() { /* ... */ }
        ~TypeMap() { /* ... */ }

        template<class U>
        U* get() {
            auto it = typemap.find(std::type_index(typeid(U)));
            return std::get<U*>(it->second);
        }
};

Consider using std::tuple

std::tuple is basically built for this task, storing a list of arbitrary types:

e.g.:

template <class... T>
class TypeMap
{
private:
    std::tuple<std::unique_ptr<T>...> values;
public:
    TypeMap() : values(std::make_unique<T>(0)...) {
    }

    template<class U> requires (std::is_same_v<U, T> || ...)
    U& get() { return *std::get<std::unique_ptr<U>>(values); }

    template<class U> requires (std::is_same_v<U, T> || ...)
    U const& get() const { return *std::get<std::unique_ptr<U>>(values); }
};

usage:

TypeMap<int, double, float> tm;
tm.get<int>() = 12;

If you want you can also store T's directly in the tuple, avoiding the additional allocations.