如何为 compile-time/static 多态性实例化类型列表
How to instantiate a list of types for compile-time/static polymorphism
我正在实现一个利用静态多态性和元编程的编译时分派器。
我有一个类型列表,我想实例化到运行时中 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
只是一个从 'expands' 到 struct<A, B, C> 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
}
有两件事我做错了:
- 我想要一个基于 MP 的列表初始化。当我更新声明中的
list
定义时,我希望我的初始化能够自动反映该列表类型。
- 我还想避免将类型名称复制为字符串文字。我曾尝试使用
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。 #2 的奖励不使用基于 #define
的诡计。
如果有人关心:此代码用于嵌入式软件。有许多不同的类型,重要的是,每种类型(例如 A
、B
、C
)都将具有相同结构的配置以从内存中加载(例如,在配置键下"A"
) - 因此想要在运行时访问类型的字符串名称的原因。
不确定您到底想要什么但是...
鉴于您至少可以使用 C++17(对于 auto
模板参数),您可以在 class 之外定义一些变量作为
static constexpr char nops[] = "NOP";
static constexpr char A[] = "A";
static constexpr char B[] = "B";
static constexpr char C[] = "C";
然后是一个简单的包装器,接受 nops
、A
、B
等作为模板参数
template <auto val>
struct wrap
{ };
然后 using
,给定模板值参数的可变列表,创建 wrap
类型的 mp_list
template <auto ... vals>
using wrapper = mp_list<wrap<vals>...>;
此时...我想,在Test
里面,你可以定义nop
和list
如下
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
中获得一个额外的 {&Test::process<wrap<D>>, 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;
}
我建议将 A、B 和 C 的 typedef
更改为结构,以便您可以在其中定义字符串。
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';
}
}
我正在实现一个利用静态多态性和元编程的编译时分派器。
我有一个类型列表,我想实例化到运行时中 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
只是一个从 'expands' 到 struct<A, B, C> mp_list
的可变参数模板。同样,mp_size
给出了 sizeof
.
可以推断,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
}
有两件事我做错了:
- 我想要一个基于 MP 的列表初始化。当我更新声明中的
list
定义时,我希望我的初始化能够自动反映该列表类型。 - 我还想避免将类型名称复制为字符串文字。我曾尝试使用
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。 #2 的奖励不使用基于 #define
的诡计。
如果有人关心:此代码用于嵌入式软件。有许多不同的类型,重要的是,每种类型(例如 A
、B
、C
)都将具有相同结构的配置以从内存中加载(例如,在配置键下"A"
) - 因此想要在运行时访问类型的字符串名称的原因。
不确定您到底想要什么但是...
鉴于您至少可以使用 C++17(对于 auto
模板参数),您可以在 class 之外定义一些变量作为
static constexpr char nops[] = "NOP";
static constexpr char A[] = "A";
static constexpr char B[] = "B";
static constexpr char C[] = "C";
然后是一个简单的包装器,接受 nops
、A
、B
等作为模板参数
template <auto val>
struct wrap
{ };
然后 using
,给定模板值参数的可变列表,创建 wrap
类型的 mp_list
template <auto ... vals>
using wrapper = mp_list<wrap<vals>...>;
此时...我想,在Test
里面,你可以定义nop
和list
如下
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
中获得一个额外的 {&Test::process<wrap<D>>, 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;
}
我建议将 A、B 和 C 的 typedef
更改为结构,以便您可以在其中定义字符串。
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';
}
}