1

I'm reading the source code of spconv, a sparse convolution library wrote by c++ (and cuda), in the source code I found a complicate template usage, and I summarize it as a minimum working example below:

#include <iostream>
#include <sstream>

template<class... T>
struct mp_list {
};

template<class T, T... I> using mp_list_c = mp_list<std::integral_constant<T, I>...>;

template<class... Ts, class F>
constexpr F mp_for_each_impl(mp_list<Ts...>, F &&f) {
    return (void) (std::initializer_list<int>{(f(Ts()), 0)...}), std::forward<F>(f);
}

template<class A, template<class...> class B>
struct mp_rename_impl {
};

template<template<class...> class A, class... T,
        template<class...> class B>
struct mp_rename_impl<A<T...>, B> {
    using type = B<T...>;
};

template<class A, template<class...> class B> using mp_rename = typename mp_rename_impl<A, B>::type;

template<class L, class F>
constexpr F mp_for_each(F &&f) {
    return mp_for_each_impl(mp_rename<L, mp_list>(), std::forward<F>(f));
}

template<int... Is, typename F>
bool dispatch_int_noexcept(int idx, F &&f) {
    static_assert(sizeof...(Is) > 0, "you need to provide at least one candidate");
    bool notFound = true;
    mp_for_each<mp_list_c<int, Is...>>([=, &notFound, &f, &idx](auto I) {
        if (decltype(I)::value == idx && notFound) {
            std::forward<F>(f)(I);
            notFound = false;
        }
    });
    return !notFound;
}

template<int ... Is, typename F>
void dispatch_int(int idx, F &&f) {
    if (!dispatch_int_noexcept<Is...>(idx, std::forward<F>(f))) {
        std::stringstream ss;
        mp_for_each<mp_list_c<int, Is...>>([=, &ss](auto I) { ss << decltype(I)::value << " "; });
        std::cout << "unknown value " << idx << ", available: " << ss.str() << std::endl;
    }
}


int main() {

    int ndim;
    std::cin >> ndim;
    dispatch_int<2, 3, 4>(ndim, [&](auto I) {
        constexpr int NDim = decltype(I)::value;
        std::cout << "using ndim= " << NDim << std::endl;
        // blablabla ... ...
    });

    return 0;
}

In main(), the function dispatch_int<2, 3, 4>(ndim, [&](auto I) {})reveives an argument ndim, and checks if ndim is in a predefined list <2, 3, 4>, if it is, then execute the following code compiled with the specific ndim.

However, I can't understand how this works, the templates defined above are really confusing. After a lot of searching, I guess it is a kind of template metaprogramming to dispatch argument, but still can't figure out the details, could someone explain how the above code works?

zhuuuuu
  • 23
  • 4
  • 2
    It's unclear what part of it you don't understand. You posted around 50 lines of code, you can't expect us to explain every one of them in detail. Please point specific code snippets . – Jan Schultke Sep 04 '20 at 08:03
  • Possibly use a template preprocessor tool such as the one suggested here? https://stackoverflow.com/questions/1139793/c-template-preprocessor-tool – Den-Jason Sep 04 '20 at 08:09
  • I believe you can be frustrated by reading such kind of code. But there is no other way than learning template syntax and semantic. It is not as hard as it looks on the first view! Some details like "which instance fits best" or type deduction can be hard, but that is not the problem in the code posted here! – Klaus Sep 04 '20 at 08:19

1 Answers1

1

So the first part, (minus useless part)

// list of type
template<class... T>
struct mp_list {
};

// list of type, which are integral_constant
// so we can see it like a list of constexpr T
// since we can use ::value on each type 
template<class T, T... I> using mp_list_c = mp_list<std::integral_constant<T, I>...>;

template<class... Ts, class F>
constexpr void mp_for_each_impl(mp_list<Ts...>, F &&f) {
     // old school 
      (void) (std::initializer_list<int>{(f(Ts()), 0)...});
    //C++17 : 
    // (f(Ts()),...); // see : https://en.cppreference.com/w/cpp/language/fold
    // it call f(x) for each x in Ts
}

template<class L, class F>
constexpr void mp_for_each(F &&f) {
    // mp_for_each_impl is needed to get the template list out of "L"
     mp_for_each_impl(L{}, std::forward<F>(f));
}

Note about "the useless part" :

template<class... Ts, class F>
constexpr F mp_for_each_impl(mp_list<Ts...>, F &&f) {
    return (void) (std::initializer_list<int>{(f(Ts()), 0)...}), std::forward<F>(f);
}

Since for_each doesn't need to return anything, it can return the function, may be it can be usefull in some case. And it mimics https://en.cppreference.com/w/cpp/algorithm/for_each

template<class A, template<class...> class B>
struct mp_rename_impl {
};

template<template<class...> class A, class... T,
        template<class...> class B>
struct mp_rename_impl<A<T...>, B> {
    using type = B<T...>;
};

template<class A, template<class...> class B> using mp_rename = typename mp_rename_impl<A, B>::type;

AFAIU, mp_rename is use to be generic, since mp_for_each_impl use mp_list the rename is used to convert "any type with a variadic template" into mp_list.

Then in :

template<int ... Is, typename F>
void dispatch_int(int idx, F &&f) {
    if (!dispatch_int_noexcept<Is...>(idx, std::forward<F>(f))) {
        std::stringstream ss;
        mp_for_each<mp_list_c<int, Is...>>([=, &ss](auto I) { ss << decltype(I)::value << " "; });
        std::cout << "unknown value " << idx << ", available: " << ss.str() << std::endl;
    }
}

the dispatch_int_noexcept<Is...>(idx, std::forward<F>(f)) do the work and the rest is for the error message.

then :

template<int... Is, typename F>
bool dispatch_int_noexcept(int idx, F &&f) {
    // just a check
    static_assert(sizeof...(Is) > 0, "you need to provide at least one candidate");
    bool notFound = true;
    mp_for_each<mp_list_c<int, Is...>>([=, &notFound, &f, &idx](auto I) {
        // look if the 'ndim' match one of the ints of dispatch_int<2, 3, 4>
        // note they are now std::integral_constant, so ::value
        if (decltype(I)::value == idx && notFound) {
            // found : call the early lambda with the integral_constant
            // that why you do the 'constexpr int NDim = decltype(I)::value;'
            std::forward<F>(f)(I);
            notFound = false;
        }
    });
    return !notFound;
}

Tell me if it helps

Martin Morterol
  • 2,560
  • 1
  • 10
  • 15