用于参数分派的 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...>>([=, ¬Found, &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...>>([=, ¬Found, &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;
}
如果有帮助请告诉我
我在看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...>>([=, ¬Found, &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...>>([=, ¬Found, &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;
}
如果有帮助请告诉我