I need an efficient way to turn a runtime-dynamic int in a range from 0 to N-1 into a template argument.
I.e., I want to create - logically speaking - a switch
that switches a number between 0
and N-1
. N
is a template parameter, so the number of cases is fixed at compile time, but it is dependent on the template argument, so I cannot just use a normal switch.
The goal is to call a template lambda for each case where the switched-upon number is passed to the lambda as a template argument. I.e., I want to go from a dynamic number to a static template argument. So in pseudo-code, I want to reach something like this:
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>();
}
}
The solution should only use templates, no macros. Of course I cannot use a normal switch
due to the varying number of cases.
How can I achieve this? Here is what I have so far.
I use a combination of std::variant
, std::make_index_sequence
, & std::integral_constant
to give me a variant of all integral constants between 0 and 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>()));
}
So now I can use IndexVariantHelper<N>::type
to get a variant of index sequences and I can use std::visit
to perform the switch. So I am almost done...:
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);
So the switching can now be done, nice! And the good news is that the resulting code is blazingly efficient. The compiler can look through all the templates and indeed generate very efficient code for the switch. It can even constant fold it - depending on the lambda of course. Here is an example: https://godbolt.org/z/v31sdKWv1
My only remaining problem is how to get the dynamic input n
into the variant. I am running out of ideas here.
Array of functor does that job
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>{});
}