For loop over template arguments/types

c++, metaprogramming, templates

Solution

Sometimes it helps to have an idea of what you are aiming for:

- you need several parameter types

- and for each parameter types, several possible "values"

And want to apply something on every single combination of values (one per parameter type at a time).

This looks like it could be expressed:

combine<
    Set<Sha256, Sha512, Sa512_256, Sha3_256, Sha3_512>,
    Set<TwoPassKeyedHash, OnePassKeyedHash, PlainHash>,
    Set<GetLeaf<8>, GetLeaf<1024>>,
    Set<algA, algB, algC>
>(runAndTime);

if `runAndTime` is an instance of:

struct SomeFunctor {
   template <typename H, typename W, typename L, typename A>
   void operator()(cons<H>{}, cons<W>{}, cons<L>{}, cons<A>{});
};

and `cons` is just a way to pass a type as a regular parameter (much easier).

Let's go ?

First, some way to pass around types (cheaply):

template <typename T>
struct cons { using type = T; };

template <typename... T>
struct Set {};

An explicit `bind` (with no magic inside):

template <typename F, typename E>
struct Forwarder {
    Forwarder(F f): inner(f) {}

    template <typename... Args>
    void operator()(Args... args) { inner(cons<E>{}, args...); }

    F inner;
}; // struct Forwarder

And now we delve into the real task at hand:

- we need to iterate on sets of types

- within a set, we need to iterate on its elements (types too)

That calls for two levels of dispatch:

template <typename FirstSet, typename... Sets, typename F>
void combine(F func);

template <typename Head, typename... Tail, typename... Sets, typename F>
void apply_set(F func, Set<Head, Tail...>, Sets... others);

template <typename... Sets, typename F>
void apply_set(F func, Set<>, Sets... others);

template <typename E, typename NextSet, typename... Sets, typename F>
void apply_item(F func, cons<E>, NextSet, Sets...);

template <typename E, typename F>
void apply_item(F func, cons<E> e);

Where `combine` is the outer (exposed) function, `apply_set` is used to iterate on the sets and `apply_item` is used to iterate on the types within a set.

The implementations are simple:

template <typename Head, typename... Tail, typename... Sets, typename F>
void apply_set(F func, Set<Head, Tail...>, Sets... others) {
    apply_item(func, cons<Head>{}, others...);

    apply_set(func, Set<Tail...>{}, others...);
} // apply_set

template <typename... Sets, typename F>
void apply_set(F, Set<>, Sets...) {}

template <typename E, typename NextSet, typename... Sets, typename F>
void apply_item(F func, cons<E>, NextSet ns, Sets... tail) {
    Forwarder<F, E> forwarder(func);

    apply_set(forwarder, ns, tail...);
}

template <typename E, typename F>
void apply_item(F func, cons<E> e) {
    func(e);
} // apply_item


template <typename FirstSet, typename... Sets, typename F>
void combine(F func) {
    apply_set(func, FirstSet{}, Sets{}...);
} // combine

For each of `apply_set` and `apply_item` we have a recursive case and a base case, though it's some kind of co-recursion here as `apply_item` calls back to `apply_set`.

And a simple example:

struct Dummy0 {}; struct Dummy1 {}; struct Dummy2 {};
struct Hello0 {}; struct Hello1 {};

struct Tested {
    Tested(int i): value(i) {}

    void operator()(cons<Dummy0>, cons<Hello0>) { std::cout << "Hello0 Dummy0!\n"; }
    void operator()(cons<Dummy0>, cons<Hello1>) { std::cout << "Hello1 Dummy0!\n"; }
    void operator()(cons<Dummy1>, cons<Hello0>) { std::cout << "Hello0 Dummy1!\n"; }
    void operator()(cons<Dummy1>, cons<Hello1>) { std::cout << "Hello1 Dummy1!\n"; }
    void operator()(cons<Dummy2>, cons<Hello0>) { std::cout << "Hello0 Dummy2!\n"; }
    void operator()(cons<Dummy2>, cons<Hello1>) { std::cout << "Hello1 Dummy2!\n"; }

    int value;
};

int main() {
    Tested tested(42);
    combine<Set<Dummy0, Dummy1, Dummy2>, Set<Hello0, Hello1>>(tested);
}

Which you can witness live on Coliru prints:

Hello0 Dummy0!
Hello1 Dummy0!
Hello0 Dummy1!
Hello1 Dummy1!
Hello0 Dummy2!
Hello1 Dummy2!

Enjoy :)

Note: it was presumed that the functor was cheap to copy, otherwise a reference can be used, both when passing and when storing it in `Forwarder`.

Edit: removed the `cons` around `Set` (everywhere it appeared), it's unnecessary.

Problem

I want to write benchmark code for several combinations of several possible classes. If I write each combination myself it becomes an unmaintainable mess. Thus I'm looking for a way to automatically combine each type via templates, something akin to the following pseudo code: ``` for (typename HashFuction : Sha256, Sha512, Sa512_256, Sha3_256, Sha3_512) { for (typename KeyingWrapper : TwoPassKeyedHash, OnePassKeyedHash, PlainHash) { for (typename InstantiatedGetLeaf: GetLeaf<8>, GetLeaf<1024>) { for (typename algorithm : algA, algB, algC) { runAndTime<HashFunction,KeyingWrapper, InstantiatedGetLeaf,algorithm>(someArgs); } } } } ``` Where `Sha256`,… ,`TwoPassKeyedHash`,… are types. The code I'm looking for is supposed to be functionally equivalent to the following: ``` runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<8>,algA>(someArgs); runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<8>,algB>(someArgs); runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<8>,algC>(someArgs); runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<1024>,algA>(someArgs); runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<1024>,algB>(someArgs); runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<1024>,algC>(someArgs); runAndTime<Sha256,OnePassKeyedHash,GetLeaf<8>,algA>(someArgs); runAndTime<Sha256,OnePassKeyedHash,GetLeaf<8>,algB>(someArgs); runAndTime<Sha256,OnePassKeyedHash,GetLeaf<8>,algC>(someArgs); // And 99 further lines… ``` With Peregring-lk's help I have come as far as ``` #include <iostream> template<typename Aux_type> void test_helper() {} template<typename Aux_type, typename Head, typename... Tail> void test_helper() { std::cout << Head::i; test_helper<Aux_type, Tail...>(); } template<typename... Args> void test() { test_helper<void, Args...>(); } struct A{ static const int i=1; }; struct B{ static const int i=2; }; int main() { test<A, B>(); return 0; } ``` but I don't yet see how I could iterate that recursion to get nested loops. Any help would be appreciated. (Edit: Code restructuring and inclusion of Peregring-lk's answer.)

Original source

Related problems