Function operating on each type of a variadic template typelist

Viewed 122

I have defined a typelist like this:

template <typename ... Types> struct typelist {};

using my_list = typelist<int, double, bool, float>;

Now I have a function template, e.g.

template<typename T>
void foo() {
    std::cout << typeid(T).name() << std::endl;
}

and want to call this for every type in the typelist:

foo<int>();
foo<double>();
foo<bool>();
foo<float>();

I tried to find a recursive way to solve this, but I am having trouble to define the correct, probably nested, variadic templates for the required foo functions. Do you have any hints for a neat solution to this problem?

3 Answers
template<class... Types> auto foo_foreach(typelist<Types...>) {
     return (foo<Types>(), ...);
}

int main() {
    foo_foreach(my_list{});
}

For a real oldschool, well, use template recursion you've attempted before:

void foo_foreach(typelist<>) {}

template<class Head, class... Tail> void foo_foreach(typelist<Head, Tail...>);

template<class Head, class... Tail> void foo_foreach(typelist<Head, Tail...>) {
    foo<Head>();
    foo_foreach(typelist<Tail...>{});
}

Here is a c++20 answer only that uses lambda template.

template <typename... Ts, typename C>
constexpr void for_types(C&& c) {
    (c.template operator()<Ts>(), ...);
}

for_types<int, float, char>([]<typename T>()
{
    std::cout << typeid(T).name() << std::endl;
});

If you need this to work with C++14 then you can use the initializer_list trick to avoid a C++17 fold expression. This will hopefully be cheaper to compile than a recursive approach:

template<class... Ts> void foo_foreach(typelist<Ts...>) {
    (void) std::initializer_list<int>{(foo<Ts>(), 0)...};
}

int main() {
    foo_foreach(my_list{});
}

Godbolt example: https://godbolt.org/z/o91Th466s

There is a good explanation of the initializer_list trick at https://blog.tartanllama.xyz/exploding-tuples-fold-expressions/

From that article:

[...] parameter packs can only be expanded in contexts which expect a syntactic list, such as initializers and function call arguments. You can’t just expand them bare in a function body. In C++17, this problem has a nice solution, but prior to that we need to use some pretty horrible hacks. [...] one possibility [...] uses std::initializer_list to create a context in which the parameter pack can be expanded.

The trick is the , 0 inside the initializer_list initializer, which evaluates the function call, and uses 0 as the initializer value.

Related