我需要一种有效的方法将 0 到 N-1 范围内的运行时动态 int 转换为模板参数。
也就是说,从逻辑上讲,我想创建一个
switch
,在 0
和 N-1
之间切换数字。 N
是一个模板参数,因此案例数在编译时是固定的,但它依赖于模板参数,所以我不能只使用普通的开关。
目标是为每种情况调用模板 lambda,其中切换的数字作为模板参数传递给 lambda。即,我想从动态数字变为静态模板参数。所以在伪代码中,我想达到这样的效果:
template<size_t N, typename Lambda>
auto switchOverN(int n, Lambda lambda) {
assert(n < N && n >= 0);
switch (n) {
case 0: return lambda<0>();
case 1: return lambda<1>();
...
case N-1: return lambda<n-1>();
}
}
解决方案应该只使用模板,而不使用宏。当然,由于案例数量不同,我不能使用正常的
switch
。
我怎样才能实现这个目标?这是我到目前为止所拥有的。
我使用
std::variant
、std::make_index_sequence
和 std::integral_constant
的组合来给出 0 到 N-1 之间所有积分常数的变体:
template<size_t I>
struct IndexVariantHelper {
template<std::size_t... I>
std::variant<std::integral_constant<int, I>...> toVariant(std::index_sequence<I...>) { return {}; }
typename type = decltype(toVariant(std::make_index_sequence<I>()));
}
所以现在我可以使用
IndexVariantHelper<N>::type
来获取索引序列的变体,并且我可以使用 std::visit
来执行切换。所以我几乎完成了...:
template<size_t N, typename Lambda>
auto switchOverN(int n, Lambda lambda) {
typename IndexVariantHelper<N>::type switcher = ... // Here's the missing part
return std::visit([&]<typename Idx>(const Idx&) {
constexpr size_t i = Idx::value;
return lambda<i>();
}, switcher);
现在可以完成切换了,太好了!好消息是生成的代码非常高效。编译器可以查看所有模板,并确实为开关生成非常有效的代码。它甚至可以不断地折叠它——当然取决于 lambda。这是一个例子:https://godbolt.org/z/v31sdKWv1
我唯一剩下的问题是如何将动态输入
n
放入变体中。我已经没有想法了。
函子数组可以完成这项工作
template <std::size_t N>
auto toIntegralConstantVariant(std::size_t n)
{
return [&]<std::size_t... Is>(std::index_sequence<Is...>){
return std::array{
+[]() -> std::variant<std::integral_constant<size_t, Is>...>{
return std::integral_constant<size_t, Is>{};
}...
}[n]();
}(std::make_index_sequence<N>{});
}