I'm implementing a compile time dispatcher which makes use of static polymorphism and metaprogramming.
I have a list of types which I would like to instantiate into a runtime std::array
.
struct Test
{
typedef std::integral_constant<int,0> nop;
typedef std::integral_constant<int,1> A;
typedef std::integral_constant<int,2> B;
typedef std::integral_constant<int,3> C;
using list = mp_list<A, B, C>; // mp_list expands to: template<A, B, C> struct {};
struct Things{
int (Test::*process)(int foo, float bar);
const std::string key;
int something;
float other;
};
typedef std::array<Things, mp_size<list>::value> Thing_list;
Thing_list thing_list;
template<typename T=nop> int process(int foo, float bar);
// stuff...
Test();
}
In the above code, mp_list
is simply a variadic template which 'expands' to struct<A, B, C> mp_list
. And likewise, mp_size
gives an mp implementation of the sizeof
.
As can be inferred, the Thing_list
is an array with a compile-time known size.
I can then specialize a template function like so:
template<> int process<Test::B>(int foo, float bar){ /* do stuff */ };
to achieve compile-time polymorphism.
The above code works well, except that to initialize it, I am stuck at doing this in the constructor:
Test::Test() thing_list({{{&Test::process<A>, "A"}, // this all should be achieved through meta-programming
{&Test::process<B>, "B"},
{&Test::process<C>, "C"}}}} )
{
// stuff
}
There are two things I'm unable to get right:
list
definition in the declaration, I would like my initialization to automatically reflect that list type.integral_constant
but the use of const char*
as a template parameter seems to be forbidden. I am left to having to duplicate declaration (triplicate, really).This answer is almost the solution: it takes a list of types and instantiates them like so:
static std::tuple<int*, float*, foo*, bar*> CreateList() {
return { Create<int>(), Create<float>(), Create<foo>(), Create<bar>() };
}
However, I'm stuck on converting from std::tuple
to std::array
.
The main question is #1. Bonus for #2 without using #define
based trickery.
If anyone cares: this code is destined for embedded software. There are dozens of different types, and importantly, each type (e.g. A
, B
, C
) will have an identically structured configuration to be loaded from memory (e.g. under a configuration key for "A"
) - hence the reason of wanting to have access to the string name of the type at runtime.
I would suggest changing the typedef
s for A, B and C to struct so you can define the string inside them.
struct A {
static constexpr int value = 1;
static constexpr char name[] = "A";
};
// Same for B and C
using list = mp_list<A, B, C>;
Then you can create a make_thing_list
template <typename... T>
static std::array<Things, sizeof...(T)> make_thing_list(mp_list<T...>) {
return {{{&Test::process<T>, T::name}...}};
}
auto thing_list = make_thing_list(list{});
Complete example
#include <string>
#include <array>
#include <iostream>
template <typename... T>
struct mp_list {};
struct Test
{
struct nop {
static constexpr int value = 0;
static constexpr char name[] = "nop";
};
struct A {
static constexpr int value = 1;
static constexpr char name[] = "A";
};
struct B {
static constexpr int value = 2;
static constexpr char name[] = "B";
};
struct C {
static constexpr int value = 3;
static constexpr char name[] = "C";
};
using list = mp_list<A, B, C>; // mp_list expands to: template<A, B, C> struct {};
struct Things{
int (Test::*process)(int foo, float bar);
const std::string key;
int something;
float other;
};
template <typename... T>
static std::array<Things, sizeof...(T)> make_thing_list(mp_list<T...>) {
return {{{&Test::process<T>, T::name}...}};
}
using Thing_list = decltype(make_thing_list(list{}));
Thing_list thing_list = make_thing_list(list{});
template<typename T=nop> int process(int foo, float bar) {
return T::value;
}
// stuff...
Test() {}
};
int main() {
Test t;
static_assert(std::is_same_v<decltype(t.thing_list), std::array<Test::Things, 3>>);
for (auto& thing : t.thing_list) {
std::cout << thing.key << (t.*thing.process)(1, 1.0) << '\n';
}
}