用于参数分派的 C++ 模板元编程

C++ template metaprogramming for argument dispatch

我在看spconv的源码,一个用c++(和cuda)写的稀疏卷积库,在源码中我发现了一个复杂的模板用法,我把它总结为一个最小工作下面的示例:

#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;
}

main()中,函数dispatch_int<2, 3, 4>(ndim, [&](auto I) {})接收一个参数ndim,并检查ndim是否在预定义列表<2, 3, 4>中,如果是, 然后执行下面用特定 ndim.

编译的代码

但是,我无法理解这是如何工作的,上面定义的模板真的很混乱。经过大量搜索,我猜这是一种分派参数的模板元编程,但仍然无法弄清楚细节,谁能解释一下上面的代码是如何工作的?

所以第一部分,(减去无用的部分)

// 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));
}

关于“无用部分”的注意事项:

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);
}

由于for_each不需要return任何东西,它可以return这个功能,在某些情况下可能会有用。它模仿 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 用于通用,因为 mp_for_each_impl 使用 mp_list 重命名用于将“具有可变参数模板的任何类型”转换为 mp_list .

然后在:

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;
    }
}

dispatch_int_noexcept<Is...>(idx, std::forward<F>(f)) 完成工作,剩下的就是错误消息。

然后:

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;
}

如果有帮助请告诉我