Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ combinatorial template expansion

Tags:

c++

templates

I want to request a series of data from a library, that requires me to specify two types:

class a;
class b;
class c;
template<typename A, typename B> void get() {....}

void get_all() {
  get<a,a>()
  get<a,b>()
  get<a,c>()
  get<b,a>()
  get<b,b>()
  // etc...
}

I want to call the get<A,B> function for every combination of a set of classes (about 10 different variations) - meaning about 100 different calls. I'd rather not code all those get() calls by hand.

Is there some way to generate these calls automatically? I suppose I could use preprocessor macros, but I was curious if there was way to generate such a combinatorial list, given a,b,c,d,... by use of template code.

Edit: Minor complication: in fact, this is all in a member function.

template<typename A, typename B> 
void Getter::get() 
{  
  auto assn = fMemberObject->RetrieveAllDataOfType<association<A,B>();
  .. do stuff with assn ...
}

void Getter::get_all() {
  get<a,a>();
}

So, code needs to deal with passing this along with the templates. Also, C++11 is probably as far as I want to push the language, if possible....

Edit 2: Turns out I want to avoid the repeated cases <a,a> and <b,b>...

like image 810
Nathaniel Avatar asked Dec 24 '22 17:12

Nathaniel


2 Answers

When in doubt on solve a template metaprogramming problem, it helps to think about how to do this in normal programming. In this case, we'd want something like:

for a in type_list:
    for b in type_list:
        foo(a,b)

So, let's translate that directly into C++. We need a way of representing a type and a typelist:

template <class T> struct tag { };
template <class... Ts> struct type_list { };

And we need a way of iterating over a type list and doing something for each type. We'll call that something a function invocation. In C++17 (in C++14 and lower, you could use the swallow/expander trick for invoking f on each tag<Ts>):

template <class... Ts, class F>
void for_each(type_list<Ts...>, F f) {
    (f(tag<Ts>{}), ...);
}

And that's... basically it actually. We need a function that we're actually calling, a typelist, and code:

template <class X, class Y>
void foo(tag<X>, tag<Y> ) {
    std::cout << __PRETTY_FUNCTION__ << '\n'; // or something more meaningful
}

int main() {
    type_list<a, b, c> types;

    for_each(types, [=](auto x){
        for_each(types, [=](auto y){
            foo(x, y);
        });});
}

Demo

C++11 version, sort of demonstrating the power of generic lambdas.


Based on the update of wanting the types not to be different, this is easy to update too. We can add an equality operator to tag:

template <class T, class U>
constexpr std::integral_constant<bool, std::is_same<T,U>::value>
operator==(tag<T>, tag<U> ) { return {}; }

template <class T, class U>
constexpr std::integral_constant<bool, !std::is_same<T,U>::value>
operator!=(tag<T>, tag<U> ) { return {}; }

Note that we're not returning bool, we're returning a type that encodes the result in the type system. We can then use this naturally:

for_each(types, [=](auto x){
    for_each(types, [=](auto y){
        if constexpr (x != y) {
            foo(x, y);
        }
    });});

foo(x,y) will only be instantiated if the two tag types are different.

like image 53
Barry Avatar answered Dec 28 '22 22:12

Barry


Updated to handle the new requirements that get() not be called with identical type parameters, and that get() be a class member function. I have called the class manager here.


You can (ab)use C++11 variadic templates to achieve this. There is probably a more concise way to do this, but it works and is (hopefully) understandable:

struct manager
{
    // Sample implementation of get(), showing the names of the types.
    template <typename A, typename B>
    void get() {
        std::cout << "A=" << typeid(A).name() << " B=" << typeid(B).name() << '\n';
    }
};

// Implementation, uses variadic templates with recursion.
namespace detail
{
    // Helper to delimit template parameter packs.
    template <typename...> struct pack;

    // Terminating case, <=1 type argument(s) means we are done.
    template <typename...>
    struct call_get_all_beta
    {   
        static void call(manager &) { }
    };

    // Invoke get<First, Second>() and recurse with <First, Tail...>.
    template <typename First, typename Second, typename... Tail>
    struct call_get_all_beta<First, Second, Tail...>
    {   
        static void call(manager &m) {
            m.get<First, Second>();
            call_get_all_beta<First, Tail...>::call(m);
        }
    };

    // Specialization to handle skipping over types that are the same.
    template <typename First, typename... Tail>
    struct call_get_all_beta<First, First, Tail...>
    {   
        static void call(manager &m) {
            call_get_all_beta<First, Tail...>::call(m);
        }
    };

    template <typename...>
    struct call_get_all_alpha;

    // Terminating case, first pack is empty.
    template <typename... B>
    struct call_get_all_alpha<pack<>, pack<B...>>
    {   
        static void call(manager &) { }
    };

    // Pass <FirstA, B...> on to call_get_all_beta, recurse with
    // <pack<TailA...>, pack<B...>>.
    template <typename FirstA, typename... TailA, typename... B>
    struct call_get_all_alpha<pack<FirstA, TailA...>, pack<B...>>
    {   
        static void call(manager &m) {
            call_get_all_beta<FirstA, B...>::call(m);
            call_get_all_alpha<pack<TailA...>, pack<B...>>::call(m);
        }
    };
}

// Helper to call the implementation detail.
template <typename... Types>
void get_all_permutations(manager &m)
{
    detail::call_get_all_alpha<detail::pack<Types...>, detail::pack<Types...>>::call(m);
}

Then we just do get_all_permutations<a, b, c>();. (Demo)

To explain how this works, detail::call_get_all_alpha takes two pack<...> template arguments, both initially contain the entire set of types. The second one remains the same but each type is peeled off of the first pack each time it recurses. The first type and the complete set of types (via the second pack) are passed to detail::call_get_all_beta, which uses the same "peeling" recursive technique to turn <A, B, C, D> into calls to get<A, B>(), get<A, C>(), and get<A, D>().

So at a higher level, detail::call_get_all_alpha is responsible for iterating the first template parameter to get(), and detail::call_get_all_beta is responsible for iterating the second template parameter.

like image 45
cdhowie Avatar answered Dec 29 '22 00:12

cdhowie