Search code examples
c++embeddedmetaprogrammingtemplate-meta-programming

How to instantiate a list of types for compile-time/static polymorphism


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:

  1. I would like to have a MP based initialization of the list. As I update the list definition in the declaration, I would like my initialization to automatically reflect that list type.
  2. I would also like to avoid having to duplicate the name of the type as a string literal. I have tried to use something like 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.


Solution

  • I would suggest changing the typedefs 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';
        }
    }