【问题标题】:C++ template metaprogramming for argument dispatch [closed]用于参数分派的 C++ 模板元编程[关闭]
【发布时间】:2020-12-23 11:39:06
【问题描述】:

我正在阅读 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&lt;2, 3, 4&gt;(ndim, [&amp;](auto I) {})接收一个参数ndim,并检查ndim是否在预定义列表&lt;2, 3, 4&gt;中,如果是,则执行以下使用特定@987654329编译的代码@。

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

【问题讨论】:

  • 不清楚您不了解其中的哪一部分。你发布了大约 50 行代码,你不能指望我们详细解释每一行。请点具体代码sn-ps。
  • 可能使用模板预处理器工具,例如这里建议的工具? stackoverflow.com/questions/1139793/…
  • 我相信你会因为阅读这样的代码而感到沮丧。但是除了学习模板语法和语义之外别无他法。它并不像第一次看到的那么难!诸如“哪个实例最适合”或类型推断之类的一些细节可能很难,但这不是此处发布的代码中的问题!

标签: c++ templates metaprogramming


【解决方案1】:

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

// 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不需要返回任何东西,它可以返回函数,也许它在某些情况下有用。它模仿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&lt;Is...&gt;(idx, std::forward&lt;F&gt;(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;
}

如果有帮助请告诉我

【讨论】:

  • 我不是!我看不到任何解释...
  • 解释在评论中。我没有进入mp_for_each,它确实如名字所暗示的那样
  • TO 要求解释可变参数模板魔术是如何工作的......你的解释非常非常顶级
  • 我已经添加和解释了核心代码,去掉了无用的返回和重命名
  • @rhaport 更好?
猜你喜欢
  • 1970-01-01
  • 2018-11-11
  • 1970-01-01
  • 1970-01-01
  • 2010-09-11
  • 2015-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多