Reputation: 1411
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.
Upvotes: 1
Views: 688
Reputation: 12928
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';
}
}
Upvotes: 1
Reputation: 66200
Not sure to understand what do you exactly want but...
Given that you can use at least C++17 (for auto
template parameters), you can define outside your class some variables as
static constexpr char nops[] = "NOP";
static constexpr char A[] = "A";
static constexpr char B[] = "B";
static constexpr char C[] = "C";
Then a simple wrapper that accept nops
, A
, B
, etc. as template parameters
template <auto val>
struct wrap
{ };
Then a using
that, given a variadic list of template value parameters, create a mp_list
of wrap
types
template <auto ... vals>
using wrapper = mp_list<wrap<vals>...>;
At this point... I suppose that, inside Test
, you can define nop
and list
as follows
using nop = wrap<nops>;
using list = wrapper<A, B, C>;
Using delegating constructor, meta-programming way to initialize your thing_list
could be the following
template <auto ... vals>
Test (mp_list<wrap<vals>...>)
: thing_list{{{&Test::process<wrap<vals>>, vals}...}}
{ }
Test () : Test{list{}}
{ }
If you modify the list
adding a D
parameter (where D
is the "D"
literal)
using list = wrapper<A, B, C, D>;
automagically the you get an additional {&Test::process<wrap<D>>, D}
element in your thing_list
.
The following is a full compiling C++17 example
#include <array>
#include <string>
#include <type_traits>
template <typename...>
struct mp_list
{ };
template <typename>
struct mp_size;
template <typename ... Ts>
struct mp_size<mp_list<Ts...>>
: public std::integral_constant<std::size_t, sizeof...(Ts)>
{ };
static constexpr char nops[] = "NOP";
static constexpr char A[] = "A";
static constexpr char B[] = "B";
static constexpr char C[] = "C";
template <auto val>
struct wrap
{ };
template <auto ... vals>
using wrapper = mp_list<wrap<vals>...>;
struct Test
{
using nop = wrap<nops>;
using list = wrapper<A, B, C>;
struct Things
{
int (Test::*process)(int foo, float bar);
const std::string key;
// int something;
// float other;
};
using Thing_list = std::array<Things, mp_size<list>::value>;
Thing_list thing_list;
template<typename T=nop> int process(int foo, float bar)
{ return 0; }
template <auto ... vals>
Test (mp_list<wrap<vals>...>)
: thing_list{{{&Test::process<wrap<vals>>, vals}...}}
{ }
Test () : Test{list{}}
{ }
};
int main ()
{
Test t;
}
Upvotes: 2