I have a templated function
template <int N>
void myfunc(int a) {
// contents compile-time optimised using N
}
which I instantiate at compile-time to make unique definitions for differing N
. At runtime, I need to invoke myfunc
with the template parameter informed by a user-given variable.
A verbose method would be:
void callOptimisedMyFunc(int n, int a) {
if (n == 0) myfunc<0>(a);
if (n == 1) myfunc<1>(a);
if (n == 2) myfunc<2>(a);
// ...
if (n == MAX) myfunc<MAX>(a);
}
which is painfully long in my case, whereby MAX
can be as large as 64
. To make matters worse, I need to create such wrappers for about ten distinct functions with unique signatures and return types! E.g.
template <int N>
long otherFunc(string x, double y) {
// ...
}
void callOptimisedOtherFunc(int n, string x, double y) {
if (n == 0) return otherfunc<0>(x, y);
if (n == 1) return otherfunc<1>(x, y);
if (n == 2) return otherfunc<2>(x, y);
// ...
if (n == MAX) return otherfunc<MAX>(x, y);
}
I ergo wish to perform this mapping of run-time to compile-time parameter metaprogrammatically.
My current unsatisfyingly verbose solution is to use log-depth macros:
#define NORETURN
#define INNER1(usernum, compnum, func, args, retcmd) \
if (usernum == compnum) retcmd func<compnum> args;
#define INNER4(usernum, compbase, func, args, retcmd) \
INNER1( usernum, compbase+0, func, args, retcmd ); \
INNER1( usernum, compbase+1, func, args, retcmd ); \
INNER1( usernum, compbase+2, func, args, retcmd ); \
INNER1( usernum, compbase+3, func, args, retcmd );
#define INNER16(usernum, compbase, func, args, retcmd) \
INNER4( usernum, compbase+0, func, args, retcmd ); \
INNER4( usernum, compbase+4, func, args, retcmd ); \
INNER4( usernum, compbase+8, func, args, retcmd ); \
INNER4( usernum, compbase+12, func, args, retcmd );
#define INNER64(usernum, func, args, retcmd) \
INNER16( usernum, 0, func, args, retcmd ); \
INNER16( usernum, 16, func, args, retcmd ); \
INNER16( usernum, 32, func, args, retcmd ); \
INNER16( usernum, 48, func, args, retcmd );
#define CALL_OPTIMISED_FUNC(n, func, ...) \
INNER64( n, func, (__VA_ARGS__), NORETURN );
#define CALL_AND_RETURN_OPTIMISED_FUNC(n, func, ...) \
INNER64( n, func, (__VA_ARGS__), return );
which permits me to run:
void callOptimisedMyFunc(int N, int a) {
CALL_OPTIMISED_FUNC( N, myfunc, a );
}
long callOptimisedOtherFunc(int N, string x, double y) {
CALL_AND_RETURN_OPTIMISED_FUNC( N, otherfunc, x, y );
}
This is still uncomfortably verbose, and does not use all the beautiful C++ metaprograming facilities available in C++17. It also seems a shame to needlessly evaluate 64 if
statements at each invocation. A more satisfying solution would be to prepare a static array of function pointers, indexed by their template parameter N
.
If I only had a single templated function to call (i.e. only myfunc
), I could use index_sequence
like so:
#include <array>
#include <utility>
template<size_t... N>
constexpr auto helper_myfunc(index_sequence<N...>) {
return array <void (*)(int), sizeof...(N)> { myfunc<N> ... };
}
constexpr auto getArrayOfMyFuncRefs() {
return helper_myfunc( make_index_sequence<MAX>() );
}
which would allow me to concisely write
void callOptimisedMyFunc(int n, int a) {
static auto funcs = getArrayOfMyFuncRefs();
funcs[n](a);
}
Alas, I would need to duplicate this definition for the other functions for which I wish to map run-time parameters to compile-time ones:
template<size_t... N>
constexpr auto helper_otherfunc(index_sequence<N...>) {
return array <long (*)(string, double), sizeof...(N)> { otherfunc<N> ... };
}
constexpr auto getArrayOfOtherFuncRefs() {
return helper_otherfunc( make_index_sequence<MAX>() );
}
long callOptimisedOtherFunc(int n, string x, double y) {
static auto funcs = getArrayOfOtherFuncRefs();
return funcs[n](x, y);
}
I seek to generalise my getArray*()
functions above to a single function which accepts any singly-integer-templated function, even with differing signatures. That would allow me to concisely define:
void callOptimisedMyFunc(int n, int a) {
static auto funcs = getArrayOfFuncRefs(myfunc);
funcs[n](a);
}
void callOptimisedOtherFunc(int n, string x, double y) {
static auto funcs = getArrayOfFuncRefs(otherfunc);
return funcs[n](x, y);
}
I am struggling to do this because I have no idea how I can pass a reference like myfunc
to a function; it lacks its template syntax (i.e. <0>
). My closest efforts do not compile:
template<size_t... N, typename RetType, typename... Args>
constexpr auto helper(index_sequence<N...>, ReturnType (*funcname)(Args...)) {
return array<RetType (*)(Args...), sizeof...(N)>{ funcname<N> ... };
}
template<typename Func>
constexpr auto getArrayOfFuncRefs(Func func) {
return helper(make_index_sequence<MAX>(), func);
}
Is there a way to achieve what I want with C++ metaprogramming, or must I fall back onto my horrible usage of macros?
One way to transform runtime value to compile time value is to use std::variant
, and then use std::visit
for the dispatch:
template <size_t Max>
constexpr auto to_integral_constant_var(std::size_t n) {
return [&]<std::size_t... Is>( std::index_sequence<Is...>)
{
using ResType = std::variant<std::integral_constant<std::size_t, Is>...>;
return std::array<ResType, sizeof...(Is)>{{
std::integral_constant<std::size_t, Is>{}...
}}[n];
}(std::make_index_sequence<Max>());
}
Mostly the same as your helper function, but to have
std::variant<
std::integral_constant<std::size_t, 0>,
std::integral_constant<std::size_t, 1>,
//..
std::integral_constant<std::size_t, Max - 1>,
>;
Then the wrapping can be done as follow:
template <std::size_t N>
void myfunc(int a) {
//...
}
void CallOptimizedMyFunc(std::size_t n, int a)
{
std::visit([&](auto N){ myfunc<N()>(a); },
to_integral_constant_var<Max>(n));
}