Suppose we have function such as
template <typename T, unsigned N> void foo();
and for simplicity assume that we know that only (constant) values N_1
, N_2
... N_k
are valid for N
.
Now, suppose I want to make that compile-time parameter a run-time one, using foo()
as a black-box, i.e. implement:
template <typename T> void foo(unsigned n);
by making foo<,>()
calls. How should I go about doing that? Obviously, I can write:
template <typename T> void foo(unsigned n) {
switch(n) {
case N_1 : foo<T, N_1>(); break;
case N_2 : foo<T, N_2>(); break;
// etc. etc.
case N_k : foo<T, N_k>(); break;
}
}
... but this makes me feel all dirty. I could use a MAP() meta-macro to generate these k lines, I suppose; but can I do anything better and less-macroish to achieve the same? Is it possible to write something like the above that's general, and works for every variadic template and a fixed sequence of constant values?
Notes:
You could make a function pointer table:
using F = void(*)();
template <class T, class >
struct Table;
template <class T, size_t... Is>
struct Table<T, std::index_sequence<Is...> > {
static constexpr F fns[] = {
foo<T, Is>...
};
};
template <class T, size_t... Is>
constexpr F Table<T, std::index_sequence<Is...> >::fns[sizeof...(Is)];
And then just invoke the one you want:
template <class T, size_t N>
struct MakeTable : Table<T, std::make_index_sequence<N>> { };
template <typename T>
void foo(unsigned n) {
MakeTable<T, MaxN>::fns[n]();
}
If the N_k
s aren't contiguous, then we can use a lambda for inline parameter unpacking:
template <class T>
void foo(unsigned n) {
using seq = std::index_sequence<N_1, N_2, ..., N_k>;
indexer(seq)([n](auto i){
if (n == i) {
f<T, i>();
}
});
}
If the above is too slow, then I guess just manually build a std::unordered_map<unsigned, void(*)()>
or something.
In these kind of situations I like to build a static table of function pointers, with a dynamic parameter deciding which one to dispatch to. Below is an implementation that achieves this, in the function foo_dynamic
. To this function, you specify the maximum value of N
you'd like to support, and it builds a static table of function pointers using some recursive templates. You then dereference into this table with your dynamic parameter.
using ftype = void (*)();
template <typename T, unsigned N> void foo()
{
std::cout << N << std::endl;
}
template <typename T, unsigned max>
struct TablePopulator
{
static void populateFTable(ftype* table)
{
table[max] = foo<T,max>;
TablePopulator<T,max-1>::populateFTable(table);
}
};
template <typename T>
struct TablePopulator<T, 0>
{
static void populateFTable(ftype* table)
{
table[0] = foo<T,0>;
}
};
template<typename T, unsigned max_N>
std::array<ftype, max_N>& initTable()
{
static std::array<ftype, max_N> table;
TablePopulator<T, max_N-1>::populateFTable(table.data());
return table;
}
template<typename T, unsigned max_N>
void foo_dynamic(unsigned actualN)
{
static auto ftable = initTable<T, max_N>();
if(actualN >= max_N)
throw std::runtime_error("Max param exceeded");
ftable[actualN]();
}
int main()
{
foo_dynamic<int, 10>(1);
foo_dynamic<int, 10>(5);
return 0;
}
EDIT: Given the constraints in the question edit, here's an approach where valid indices are specified manually, which uses an unordered_map
instead of an array:
using ftype = void (*)();
template <typename T, unsigned N> void foo()
{
std::cout << N << std::endl;
}
template<typename T, size_t ... Indices>
void foo_dynamic_indices(size_t actual_index)
{
static std::unordered_map<size_t, ftype> fmap = {{Indices, foo<T,Indices>}...};
auto fIt = fmap.find(actual_index);
if(fIt == fmap.end())
throw std::runtime_error("Index not found");
fIt->second();
}
int main()
{
foo_dynamic_indices<int, 0, 3, 400, 1021, 10000000>(10000000);
foo_dynamic_indices<int, 0, 3, 400, 1021, 10000000>(4); //Exception
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With