Reputation: 65046
I'd like to convert a runtime value int v
into a call to a corresponding function with non-type template parameter v
, e.g., template <int v> void hello()
.
Here's the brute force way of writing it:
using fn_type = void();
template <int v>
void hello() {
// of course ITRW this function requires v to be a
// constexpr value
printf("hello: %d\n", v);
}
static std::array<fn_type *, 3> lut = {
hello<0>,
hello<1>,
hello<2>
};
void hello_dispatch(int v) {
lut[v](); // we don't handle OOB values b/c we are naughty like that
}
I can live with that for 3 values, but this gets impractical with more values, or when the the limit is itself calculated from some other compile-time value.
How can initialize the LUT2 at compile-time without explicitly listing the various instantiations hello<0>, hello<1>, ...
in the initializer?
This is what I came up with:
template <size_t I, size_t MAX>
constexpr void make_helper(std::array<fn_type *, MAX>& a) {
if constexpr (I < MAX) {
a[I] = hello<I>;
make_helper<I + 1, MAX>(a);
}
}
template <size_t MAX>
constexpr std::array<fn_type *, MAX> make_lut() {
std::array<fn_type *, MAX> ret{};
make_helper<0, MAX>(ret);
return ret;
}
static constexpr std::array<fn_type *, 3> lut2 = make_lut<3>();
There has got to be something simpler, better and more idiomatic in C++17 - in particular, without needing recursion.
2 Or, in case this an an XY problem, how can I implement hello_dispatch
without a LUT (but with at least the efficiency of the LUT).
Upvotes: 2
Views: 1199
Reputation: 66230
And now, for something completely different...
If you can use C++20, you can use template-lambdas and you can completely avoid the lut
array
#include <iostream>
#include <utility>
template <int v>
void hello()
{ std::cout << "hello: " << v << std::endl; }
void hello_dispatch (int v)
{
[&]<int ... Is>(std::integer_sequence<int, Is...> const &)
{ ((v == Is ? (hello<Is>(), 0) : 0), ...); }
(std::make_integer_sequence<int, 100u>{}); // 100 is top limit (former lut size)
}
int main ()
{
hello_dispatch(42);
}
If you can use only C++17... not so elegant but you can use a recursive generic lambda
#include <iostream>
#include <utility>
template <int v>
void hello()
{ std::cout << "hello: " << v << std::endl; }
template <int I>
using IC = std::integral_constant<int, I>;
void hello_dispatch (int v)
{
auto lf = [&](auto self, auto ic)
{ if constexpr ( ic < 100 )
v == ic ? (hello<ic>(), 0)
: (self(self, IC<ic+1>{}), 0); };
lf(lf, IC<0>{});
}
int main ()
{
hello_dispatch(42);
}
Upvotes: 0
Reputation: 173004
You can initialize the std::array
directly, i.e. don't need to assign the elements one by one with recursion.
template<std::size_t... I>
constexpr auto make_helper(std::index_sequence<I...>) {
return std::array<fn_type *, sizeof...(I)> { hello<I>... };
}
template <std::size_t MAX>
constexpr auto make_lut() {
return make_helper(std::make_index_sequence<MAX>{});
}
Upvotes: 4
Reputation: 4735
Use std::integer_sequence
and fold expressions:
template <int... Is>
constexpr std::array<fn_type*, sizeof...(Is)> make_lut(std::integer_sequence<int, Is...>) {
std::array<fn_type*, sizeof...(Is)> res{};
((res[Is] = &hello<Is>), ...);
return res;
}
template <int Max>
constexpr auto make_lut() {
return make_lut(std::make_integer_sequence<int, Max>{});
}
static constexpr auto lut2 = make_lut<3>();
std::make_integer_sequence<int, N>{}
creates an object of type std::integer_sequence<int, 0, 1, 2, ..., N - 1>
. hello<N>
to res[N]
with the fold expression res[Is] = &hello<Is>
. ...
fold.Upvotes: 2