【问题标题】:How to instantiate a list of types for compile-time/static polymorphism如何实例化编译时/静态多态的类型列表
【发布时间】:2021-03-28 11:07:26
【问题描述】:

我正在实现一个使用静态多态性和元编程的编译时调度程序。

我有一个类型列表,我想将其实例化到运行时 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();
}

在上面的代码中,mp_list 只是一个“扩展”为struct&lt;A, B, C&gt; mp_list 的可变参数模板。同样,mp_size 给出了sizeof 的 mp 实现。

可以推断,Thing_list 是一个具有编译时已知大小的数组。

然后我可以像这样专门化一个模板函数:

template<> int process<Test::B>(int foo, float bar){ /* do stuff */ };

实现编译时多态性。

上面的代码运行良好,除了初始化它,我坚持在构造函数中这样做:

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
}

有两件事我无法做到:

  1. 我想要一个基于 MP 的列表初始化。当我更新声明中的 list 定义时,我希望我的初始化能够自动反映该列表类型。
  2. 我还想避免将类型名称复制为字符串文字。我曾尝试使用integral_constant 之类的东西,但似乎禁止使用const char* 作为模板参数。我不得不重复声明(真的是一式三份)。

This 答案几乎是解决方案:它需要一个类型列表并像这样实例化它们:

static std::tuple<int*, float*, foo*, bar*> CreateList() {
return { Create<int>(), Create<float>(), Create<foo>(), Create<bar>() };
}

但是,我坚持从 std::tuple 转换为 std::array

主要问题是#1。不使用基于 #define 的诡计的 #2 奖励。

如果有人关心的话:这段代码是为嵌入式软件设计的。有几十种不同的类型,重要的是,每种类型(例如ABC)都将具有从内存中加载的相同结构的配置(例如,在"A" 的配置键下) - 因此想要在运行时访问类型的字符串名称的原因。

【问题讨论】:

  • 除非您在某处明确指定,否则您永远无法将变量/类型的名称作为字符串获取。
  • 对于提出的一般问题的方法,我会做类似auto thing_list = make_thing_list&lt;list&gt;();
  • CreateList 如我提供的链接中所示,确实是make_thing_list&lt;list&gt;(),但是,它会创建一个std::tuple。对于我的生活,我不能将元组变成数组。

标签: c++ embedded metaprogramming template-meta-programming


【解决方案1】:

不确定你到底想要什么,但是......

鉴于您至少可以使用 C++17(对于 auto 模板参数),您可以在类外部定义一些变量为

static constexpr char nops[] = "NOP";
static constexpr char A[] = "A";
static constexpr char B[] = "B";
static constexpr char C[] = "C";

然后是接受nopsAB 等作为模板参数的简单包装器

template <auto val>
struct wrap
 { };

然后是using,给定模板值参数的可变参数列表,创建wrap 类型的mp_list

template <auto ... vals>
using wrapper = mp_list<wrap<vals>...>;

此时...我想,在Test 内部,您可以定义noplist 如下

using nop = wrap<nops>;

using list = wrapper<A, B, C>;

使用委托构造函数,元编程方式来初始化您的thing_list 可能如下

template <auto ... vals>
Test (mp_list<wrap<vals>...>) 
   : thing_list{{{&Test::process<wrap<vals>>, vals}...}}
 { }

Test () : Test{list{}}
 { }

如果修改list 添加D 参数(其中D"D" 文字)

using list = wrapper<A, B, C, D>;

您的thing_list 中会自动获得一个额外的{&amp;Test::process&lt;wrap&lt;D&gt;&gt;, D} 元素。

以下是完整的编译C++17示例

#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;
 }

【讨论】:

    【解决方案2】:

    我建议将 A、B 和 C 的 typedefs 更改为 struct,以便您可以在其中定义字符串。

    struct A {
        static constexpr int value = 1;
        static constexpr char name[] = "A";
    };
    
    // Same for B and C
    
    using list = mp_list<A, B, C>;
    

    然后你可以创建一个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{});
    

    完整示例

    #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';
        }
    }
    

    【讨论】:

    • 很好,我真的很喜欢thing_list 的内联初始化而不需要弄乱构造函数。
    • 这可能是一个不同的问题,但g++ 8.3.0 允许我编译它,但链接器说undefined reference to 'Test::A::name'。如果我改用static consexpr char *name = "A";,它可以正常链接,但我收到编译器警告(ISO C++ forbids converting string constant to char*。有什么想法吗?
    • 尝试编辑您的帖子(不确定 SO 是否关闭):更改为 static constexpr char const * const name = "A"; 可解决此问题。
    • @MB。这方面的规则在 c++11、14 和 17 中发生了微妙的变化。我相信这段代码在 c++17 或更高版本中应该可以编译。原因是从那时起constexpr 也意味着inline 是一个静态成员。当问题没有指定版本时,我通常认为 c++17 是可以的。
    猜你喜欢
    • 2011-08-20
    • 1970-01-01
    • 2012-08-03
    • 2010-11-14
    • 2021-08-27
    • 1970-01-01
    • 2012-09-23
    相关资源
    最近更新 更多