如何在 C++ 中将 运行 时间类型鉴别器映射到模板实例(无需手动枚举它们)?
How in C++ to map run-time type discriminators to template instances (without manually enumerating them all)?
给定 enum
个代表各种小类型的类型鉴别器:
enum TypesEnum {
IntT,
DoubleT,
ShortStringT
};
假设我有一个template SomeType<typename A, typename B, typename C>
。这是我读写一组内存映射文件的类型,其 layout/stride 由类型决定;运行时类型存储为上面枚举鉴别器的三元组。
我需要编写各种工具来加载这些文件并对它们执行操作,例如 SomeType<A,B,C> => SomeType<B,A,C>
等等。因此,在这些工具中,我有一个相当笨拙的层,它将磁盘上的类型鉴别器转换为使用正确类型的模板实例实现的通用 lambdas 包装操作。
这看起来像:
static std::map< std::tuple<Discrim, Discrim, Discrim>, some_op_fn_t > =
{
{std::make_tuple(IntT, DoubleT, ShortStringT), SomeOperation<int,double,char[16]>() },
std::make_tuple(IntT, IntT, ShortStringT), SomeOperation<int,int,char[16]>() },
...
};
... look up the correct function pointer and call it with the path to the files ...
其中 typedef std::function<bool(void)> some_op_fn_t
,其在 template<A,B,C> class SomeOperation
中的实现做了一堆对磁盘有副作用的工作。
现在,随着类型列表和不同操作数量的增加,这很快就会变得非常乏味。诀窍是我不能使用虚拟继承来简单地有一个类型擦除的SomeType
,它在abstract/virtual值类型上运行;进行间接寻址和指针追逐太慢了。我需要航位推算和连续的打包值数据,适合(在浮点数和双精度数的情况下)直接传递给 BLAS。
是否有自动创建 interfaces/layers 这样的技术?类型级组合之类的东西会有所帮助,我可以将枚举连接到类型一次,然后展开映射的所有实例。那可能吗?
最坏的情况我可以编写一个脚本来生成代码,但是…
首先,一个简单的部分,类型和枚举值之间的映射:
template <typename T> struct EnumValue;
template <> struct EnumValue<int> : std::integral_constant<TypesEnum, IntT> {};
template <> struct EnumValue<double> : std::integral_constant<TypesEnum, DoubleT> {};
template <> struct EnumValue<char[16]> : std::integral_constant<TypesEnum, ShortStringT> {};
然后是一个简单的辅助函数:
using TupleT = std::tuple<int, double, char[16]>;
template <typename ... Ts> // Might be T1, T2, T3, but lazy to type
constexpr auto make_my_pair()
{
return std::make_pair(std::make_tuple(EnumValue<Ts>::value...), &SomeOperation<Ts...>);
}
现在笛卡尔积使用index_sequence
template <std::size_t I>
constexpr std::pair<TypesEnumTuple, some_op_fn_t>
make_my_pair()
{
constexpr std::size_t N = std::tuple_size<TupleT>();
return make_my_pair<
std::tuple_element_t<(I / (N * N)) % N, TupleT>,
std::tuple_element_t<(I / N) % N, TupleT>,
std::tuple_element_t<(I / 1) % N, TupleT>
>();
}
template <std::size_t ... Is>
std::map<TypesEnumTuple, some_op_fn_t>
make_my_map(std::index_sequence<Is...>)
{
return {make_my_pair<Is>()...};
}
最后:
// 27 = std::tuple_size<TupleT>() * std::tuple_size<TupleT>() * std::tuple_size<TupleT>()
// as we have T1, T2, T3
static const std::map<TypesEnumTuple, some_op_fn_t> m =
make_my_map(std::make_index_sequence<27>());
给定 enum
个代表各种小类型的类型鉴别器:
enum TypesEnum {
IntT,
DoubleT,
ShortStringT
};
假设我有一个template SomeType<typename A, typename B, typename C>
。这是我读写一组内存映射文件的类型,其 layout/stride 由类型决定;运行时类型存储为上面枚举鉴别器的三元组。
我需要编写各种工具来加载这些文件并对它们执行操作,例如 SomeType<A,B,C> => SomeType<B,A,C>
等等。因此,在这些工具中,我有一个相当笨拙的层,它将磁盘上的类型鉴别器转换为使用正确类型的模板实例实现的通用 lambdas 包装操作。
这看起来像:
static std::map< std::tuple<Discrim, Discrim, Discrim>, some_op_fn_t > =
{
{std::make_tuple(IntT, DoubleT, ShortStringT), SomeOperation<int,double,char[16]>() },
std::make_tuple(IntT, IntT, ShortStringT), SomeOperation<int,int,char[16]>() },
...
};
... look up the correct function pointer and call it with the path to the files ...
其中 typedef std::function<bool(void)> some_op_fn_t
,其在 template<A,B,C> class SomeOperation
中的实现做了一堆对磁盘有副作用的工作。
现在,随着类型列表和不同操作数量的增加,这很快就会变得非常乏味。诀窍是我不能使用虚拟继承来简单地有一个类型擦除的SomeType
,它在abstract/virtual值类型上运行;进行间接寻址和指针追逐太慢了。我需要航位推算和连续的打包值数据,适合(在浮点数和双精度数的情况下)直接传递给 BLAS。
是否有自动创建 interfaces/layers 这样的技术?类型级组合之类的东西会有所帮助,我可以将枚举连接到类型一次,然后展开映射的所有实例。那可能吗?
最坏的情况我可以编写一个脚本来生成代码,但是…
首先,一个简单的部分,类型和枚举值之间的映射:
template <typename T> struct EnumValue;
template <> struct EnumValue<int> : std::integral_constant<TypesEnum, IntT> {};
template <> struct EnumValue<double> : std::integral_constant<TypesEnum, DoubleT> {};
template <> struct EnumValue<char[16]> : std::integral_constant<TypesEnum, ShortStringT> {};
然后是一个简单的辅助函数:
using TupleT = std::tuple<int, double, char[16]>;
template <typename ... Ts> // Might be T1, T2, T3, but lazy to type
constexpr auto make_my_pair()
{
return std::make_pair(std::make_tuple(EnumValue<Ts>::value...), &SomeOperation<Ts...>);
}
现在笛卡尔积使用index_sequence
template <std::size_t I>
constexpr std::pair<TypesEnumTuple, some_op_fn_t>
make_my_pair()
{
constexpr std::size_t N = std::tuple_size<TupleT>();
return make_my_pair<
std::tuple_element_t<(I / (N * N)) % N, TupleT>,
std::tuple_element_t<(I / N) % N, TupleT>,
std::tuple_element_t<(I / 1) % N, TupleT>
>();
}
template <std::size_t ... Is>
std::map<TypesEnumTuple, some_op_fn_t>
make_my_map(std::index_sequence<Is...>)
{
return {make_my_pair<Is>()...};
}
最后:
// 27 = std::tuple_size<TupleT>() * std::tuple_size<TupleT>() * std::tuple_size<TupleT>()
// as we have T1, T2, T3
static const std::map<TypesEnumTuple, some_op_fn_t> m =
make_my_map(std::make_index_sequence<27>());