使用可变模板实现 TypeLists 和 TypeList 工具箱
本文介绍使用编译时类型列表进行元编程。
引言
typelist
是编译时类型的集合。它是元编程中用于在编译时生成代码的强大构造。我从 Andrei Alexandrescu 的著作《Modern C++ Design》[1] 中学习到了 typelists。这本书提供了 typelist
的定义、一些处理它的工具以及使用列表的示例。
在本书的帮助下,我能够使用 typelists
在 C++ 对象工厂模式中注册 Creator 函数,检查函数参数的类型,甚至在编译时初始化组合框。
本质上,typelist
是一个具有可变数量模板参数(元素)的模板,无论您如何定义它。因此,当 ISO 在 C++11 中引入可变模板时,使用这些模板来重写 typelist
和相关工具非常诱人。
然后,微软传来一个福音:免费的 Visual Studio 2015 Community Edition 附带 C++ 14 编译器。所有必需的元素都已就位,所以我决定尝试一下。
首先,我搜索了“typelist
”这个词。我发现这个领域相当拥挤。许多作者都写过关于列表的文章,并开发了处理它们的工具。很自然,每位作者都为 typelist 使用了自己的名称和定义,以及自己的一套处理列表的工具。当然,所有工具都运行良好,而且在此过程中有很多精彩有趣的内容。
似乎我唯一能做的就是把它们都整理在一起。我开始工作,发现事情并非那么简单。确实,有些工具只需要改个名字。但我发现我需要的其他工具根本没有原型,有些工具必须完全重写。
所以,你将看到结果。
致谢
可以看到,由于作者众多,谁最先说“啊哈!”这个问题非常棘手。例如,您为借用的模板结构引入了一个新的部分特化。没有这个特化,借用的模板就无法与您的 typelist
定义一起工作。您将如何引用这个东西?哪些是借用的,哪些是新的?
总之,TypeList 工具箱的清单很大程度上受到了我下面列出的来源的影响。如果我在来源中看到了某个工具,我就会对自己说:“我会在我的工具箱中包含类似的东西。”但我的代码有时会有所不同。当然,所有从其他作者那里借用的工具都会被恰当地标记。
我使用的来源列表
[1] Andrei Alexandrescu 的著作《Modern C++ Design》,Addison-Wesley 出版社。第三章专门介绍 TypeLists
。这本书写于 C++11 之前,但它帮助我制定了 Typelist 工具箱的清单。
[2] Frank B. Brokken 的《C++ Annotations v. 10.3.0》
,格罗宁根大学信息技术中心,2015 年 8 月 16 日。您可以在此网页上选择版本并下载 C++ Annotations 的 PDF 文件。在“高级模板使用”一章中,有“模板 TypeList 处理”和“使用 TypeList”的子章节。基本上,作者使用可变模板重写了 Andrei Alexandrescu 的工具。我用它来为 Typelist 工具箱的模板清单添加工具。我的工具箱中的一些模板也是从这些章节中借用的原型。
[3] Eric Niebler 的《Meta User Manual》和《Tiny Metaprogramming Library》。这是一个非常优秀且全面的库,其中包含 typelists(命名为 'lists')作为其数据类型。我从那里借用了 typelist 的定义。我还将这个库作为添加工具到工具箱清单的来源,并作为工具箱中一些模板的原型。我不知道为什么,但我在 Visual Studio 2015 Community 中的 VC++ 编译器无法接受库 LAZY 命名空间中的大多数结构,但我能够阅读代码。
[4] Peter Dimov 的《Simple C++ 11 Metaprogramming》。我借用了模板来将 TypeList 转换为 std::tuple,以及将 std::tuple 转换为 TypeList。
[5] Scott Meyers 的《Effective Modern C++》。这本书的前四章可以免费下载 PDF 文件(点击此处获取 URL)。这些章节包含了关于推导模板参数规则的非常有用的信息。我写工具模板和使用示例时需要用到它。
[6] geoyar 在 CodeProject 上发表的文章《Applying Ant Colony Optimization Algorithms to Solve the Traveling Salesman Problem》。这是我的文章,我从中摘录了内容,展示了如何使用 Typelist 注册具有对象工厂的类。
背景
了解 C++ 11 和 C++ 14 的模板和可变模板将会有所帮助。 C++ 参考手册可能会非常有帮助。此外,您还需要一个 C++ 14 编译器。我使用的是 Microsoft Visual Studio 2015 Community Visual C++。它非常棒而且是免费的。
使用代码
所有代码都在头文件 TypeList.hpp 中。您需要将其包含在您的项目中以及您的源 (.cpp) 文件中。该头文件包含了 C++14 的 <type_traits> 头文件。
所有模板都在“typelist”命名空间中,因此您需要包含“using namespace typelist;”,或者在您的源文件中使用模板的完全限定名称。
我将我的 typelist 命名为“tlist”。在下面的文本中(代码中不是),我还使用术语“typelist”、“list”和“TypeList”。所有这些术语都指类型“tlist”。
首先,您需要定义您的 typelist 的特化,例如 using MyList = tlist<char, int, MyClass>;
。
之后,您可以使用头文件中的工具来完成您想要和需要做的事情。
所有工具都是元函数——模板结构,为 typelists 部分特化。
如果类型列表操作的结果是一个类型(例如列表元素类型),您可以通过工具的成员 'type'
访问结果,如下所示:
using type_at_4 = tlist_type_at<4, MyList>::type;
如果结果是一个整数(例如类型在类型列表中的索引),则成员 'type'
是 std::integral_constant
模板。附加成员 'value_type'
和 'value'
指的是结果的 value_type
和值,它们是 std::integral_constant
模板成员的副本。所有 value
成员都声明为 static constexpr value_type value = type::value
。
“TypeList.hpp”中有一个包含所有工具的表格:>
工具 | 定义 | 成员 | 用法 | 注意 |
TypeList 定义 [3] |
template <typename... Ts> tlist |
type, |
using MyList = constexpr auto szMyList = MyList::size(); |
type 是 TypeList 本身 size() 返回列表中的元素数量 |
检查列表是否为空 | template <class List> struct tlist_islistempty |
type, |
constexpr auto bEmpty = tlist_islistempty<<MyList>::value; |
type 是 std::true_type 或 std::false_type |
检查类型是否为 TypeList | template <class List> struct is_list | type, value_type value |
constexpr auto bIsList = is_list<<MyList>::value; |
type 是 std::integral_constant<bool, value>, |
检查列表是否为列表的列表 | template <typename List> is_listoflists |
type, value_type, value |
constexpr auto bLL = is_listoflists<MyList>::value |
type 是 std::integral_constant<bool, value>, value_type 是 bool, value 是 true 或 false. |
按索引访问元素 [2] |
template <size_t idx, class List> |
type |
using type_at_3 = tlist_type_at<3, MyList>::type; |
type 是索引 idx 处列表元素的类型。如果索引超出列表边界,则会引发 static_assert。 |
在 TypeList 中搜索类型 T 的第一次出现 |
template <typename T, class List> |
type, |
constexpr auto idx = tlist_index_of<<double, MyList>::value; |
type 是 std::integral_constant<int, value> |
在 TypeList 中搜索类型不为 T 的第一次出现 | template <typename T, class List> struct tlist_index_of_not |
type, value_type, value |
constexpr auto idx = tlist_index_of_not<char, MyList>::value |
type 是 std::integral_constant<int, value> value_type 是 int value 是类型不等于 T 的第一次出现的位置索引,如果 TypeList 只包含类型 T 的元素或为空,则为 -1。 |
获取 TypeList 中的第一个元素 |
template <class List> |
type |
using front_type = tlist_front<<MyList>::type; |
返回列表的第一个元素。如果列表为空,则引发 static_assert |
获取 TypeList 中的最后一个元素 | template <class List> struct tlist_back |
type |
using back_type = tlist_back<MyList>::type; |
返回列表的最后一个元素。如果列表为空,则引发 static_assert |
在前面添加元素 | template <typename T, class List> struct tlist_push_front |
type | using NewList = tlist_push_front<double, MyList>::type |
type 是添加后的列表 |
在后面添加元素 | template <typename T, class List> struct tlist_push_back |
type | using NewList = tlist_push_back<long, MyList>::type |
type 是添加后的列表 |
从列表中移除类型为 T 的元素 [2] |
template <typename T, class List> struct tlist_erase_type |
type | using NewList = tlist_erase_type<double, MyList>::type; |
删除列表中类型 T 的第一次出现。如果列表中不存在该类型,则返回原始列表。 |
删除给定索引处的元素 [2] |
template <size_t idx, class List> struct tlist_erase_at |
type | using NewList = tlist_erase_at<3, MyList>::type; |
删除索引 idx 处的元素; 如果索引超出列表边界,则引发 static_assert。 |
从列表中删除第一个元素 | template <class List> struct tlist_pop_front |
type | using NewList = tlist_pop_front<MyList>::type; | type 是不包含第一个元素的 TypeList(结果可能是一个空列表),或者如果作用于空列表,则为空列表。 |
从列表中删除最后一个元素 | template <class List> struct tlist_pop_back |
type | using NewList = tlist_pop_back<MyList>::type; | type 是不包含最后一个元素的 TypeList(结果可能是一个空列表),或者如果作用于空列表,则为空列表。 |
删除所有元素 类型为 T 的 |
template <typename T, class List> struct tlist_erase_all template <typename T, class List> struct tlist_deeperase_all |
type | using NewList = tlist_erase_all<char, MyList>::type; using NewList = tlist_deeperase_all<char, MyList>::type |
如果 MyList 是列表的列表,则第一个模板将列表元素作为整体类型处理;第二个版本从列表的列表的每个列表中删除所有类型 T。 |
删除所有重复项 | template <class List> struct tlist_erase_dupl template <class List> struct tlist_deeperase_dupl |
type | using NewList = tlist_erase_dupl<MyList>::type; using NewList = tlist_deeperase_dupl<MyList>::type |
如果 MyList 是列表的列表,则第一个模板将列表元素作为整体类型处理;第二个版本删除列表的列表元素(列表)内部的重复项。 |
删除列表中前 k 个元素 | template <size_t k, class List> struct tlist_erase_firsts |
type | using NewList = tlist_erase_firsts<4, MyList>::type; |
最多从 TypeList 的前面删除 k 个元素,如果 k ≥ 列表大小,则返回空列表。 如果 k == 0,则引发 static_assert |
删除列表中最后 k 个元素 | template <size_t k, class List> struct tlist_erase_lasts |
type | using NewList<4, MyList>::type | 最多从 TypeList 的尾部删除 k 个元素,如果 k ≥ 列表大小,则返回空列表。 如果 k == 0,则引发 static_assert |
将元素 T 的第一次出现替换为元素 R | template <typename R, typename T, class List> struct tlist_replace_first | type | using NewList = tlist_replace_first<long long, float, MyList>::type; |
如果 TypeList 中不存在元素 T,则不执行任何操作。 |
将索引 k 处的元素替换为元素 R | template <size_t k, typename R, class List> struct tlist_replace_at |
type | using NewList = tlist_replace_at<4, double, MyList>::type; |
如果索引超出边界,则引发 static_assert 。 |
将元素 T 的所有出现替换为元素 R | template <typename R, typename T, class List> struct tlist_replace_all template <typename R, typename T, class List struct tlist_deepreplace_all |
type | using NewList = tlist_replace_all<int, char, MyList>::type using NewList = tlist_deepreplace_all<MyList>::type |
第一个模板将 T 的所有出现替换为 R;它将 T 作为整体类型处理。第二个模板接受普通列表和列表的列表。它将普通列表的处理方式与第一个模板类似,但如果 MyList 是列表的列表,则第二个版本会替换列表的列表元素(列表)中的所有元素 T。 |
计算类型 T 的所有出现次数 在 TypeList 中 |
template <typename T, class List> struct tlist_count_type | type, value_type value |
constexpr auto occurrence_float = tlist_count_type<float, MyList>::value; |
如果列表中不存在类型 T,则 value = 0; type = std::integral_constant<size_t, value> value_type 是 size_t |
计算普通 TypeList 中或列表的列表的每个元素内部类型 T 的所有出现次数 | template <typename T, class List> struct tlist_deepcount_type |
type | using res_cnt = tlist_deepcount_type<int, MyList>::type |
type 对于普通 TypeList 是 std::integer_constant<size_t, cnt> ,对于列表的列表是 std::integer_sequence<size_t, cnts...>。 |
获取 TypeList 中类型 T 的所有索引 | template <typename T, class List> struct tlist_all_idx template <typename T, class List> struct tlist_all_deepidx |
type | using idx_seq = tlist_all_idx<double, MyList>::type; using idx_seqs = tlist_all_deepidx< long, MyList>::type |
第一个模板将列表元素作为整体类型处理, type 是 std::integer_sequence<size_t, Is...>。 如果没有类型 T 的元素,则序列为空。 第二个模板接受普通列表和列表的列表。它将列表的列表的每个列表作为单独的列表处理,并将结果 std::integer_sequences 打包到 TypeList 中。 对于包含列表和其他类型的混合元素的列表,第二个工具返回与第一个工具相同的结果。 |
测试列表是否为 integer_sequence 的列表 | template <class List> is_listofiseqs |
type, value_type, value |
constexpr auto bLSeq = is_listofiseqs<MyList>::value |
type 是 std::integral_constant<bool, value>. |
反转 TypeList 中元素的顺序 | template <class List> struct tlist_reverse |
type | using NewList = tlist_reverse<MyList>::type; | |
将列表的列表转换为普通 TypeList | template <class List> struct tlist_flatten_list |
type | using lst_flattened = tlist_flatten_list<MyListOfLists>::type |
如果参数不是列表的列表,则引发 static_assert。 |
连接 TypeLists | template <class... Lists> struct tlist_concat_lists<Lists...> |
type | using NewList = tlist_concat_lists<List1, List2, List3>::type; |
如果并非所有 Lists 都是 tlist 类型,则引发 static_assert。 |
将第二个 TypeList 中前 k 个元素添加到第一个 TypeList 的前面 |
template <size_t k, class List1, class List2> struct tlist_move_firsts |
type | using MyIncreasedList = tlist_move_firsts<4, MyList1, MyList2>>::type |
要传输的元素数量必须 > 0;否则会引发 static_assert |
将 TypeList 分割成多个 TypeLists | template <class List, size_t... Ls> struct tlist_split |
type | using NewList = tlist_split<MyList, 2, 3, 1>::type; |
Ls... 是新列表中的元素数量。它们不包括最后一个列表中元素的数量。 type 是结果列表的列表。在示例用法中,结果是大小为 2、3、1 的列表的列表,以及 MyList 的其余元素; 如果大小总和≥原始列表中的元素数量,则引发 static_assert。如果大小序列包含零,也会引发 static_assert。 |
获取最大 TypeList 元素的大小 | template <class List> struct tlist_max_type_size |
type, value_type, value |
constexpr auto max_sz = tlist_max_type_size<MyList>::value; |
该模板分析列表元素作为整体类型。 type 是 std::integral_constant<size_t, sizeof(max element)>, value_type = size_t. 如果模板在空列表上调用,则 value = 0。 |
获取列表中每个列表的最大元素大小 | template <class List> struct tlist_max_type_deep_size |
type | using iseq_max_sz = tlist_max_type_deep_size<MyListOfLists>::type; | 如果模板在空列表上调用,则 value = 0。 该工具将前一个模板应用于列表的列表中的每个列表,并将结果打包到 type = std::integer_sequence。 如果参数不是列表的列表,而是 tlist,则该工具的功能与前一个模板相同 并返回 std::integral_constant<size_t, value>。 |
获取给定大小的第一个元素 | template <size_t k, class List> struct tlist_firsttype_of_size template <size_t k, class List> struct tlist_firsttypedeep_of_size |
type | using type_sz_2 = tlist_type_of_size<2, MyList>::type; using types_sz_8 = tlist_firsttypedeep_of_size<8, MyListOfLists>::type |
第一个模板将列表的元素作为整体类型进行分析;它返回大小为 k 的第一个类型。如果不存在这样的类型,则 type = std::false_type。 第二个模板对列表的列表进行特殊处理:它在列表的列表的每个列表内部搜索大小为 k 的第一个元素, type 是找到的类型的列表。如果某个特定的列表的列表不包含大小为 k 的元素,则结果中会插入 std::false_type。 |
获取给定大小 k 的所有元素 | template <size_t k, class List> struct tlist_all_types_of_size template <size_t k, class List> struct tlist_all_typesdeep_of_size |
type | using types_of_size_4 = tlist_all_types_of_size<4, MyList>::type using types_of_size_4 = tlist_all_typesdeep_of_size<MyList>::type |
第一个模板将列表的元素作为整体类型进行分析;它返回大小为 k 的类型列表。如果不存在大小为 k 的类型,则结果为空列表。。 对于列表的列表,第二个模板将搜索应用于列表的列表中的每个列表;结果是找到的类型的列表的列表。结果中会删除所有重复项。 |
获取所有大于或等于 大小 k 的类型 |
template <bool bEqual, size_t k, class List> struct tlist_types_greater template <bool bEqual, size_t k, class List> struct tlist_typesdeep_greater |
type | using all_types_greater_2 = tlist_types_greater<true, 2,MyList>::type; using all_types_greater_4 = tlist_typesdeep_greater<false, 4, MyList>::type |
type 是大小大于 k 的元素组成的 TypeList 如果 bEqual == false,或者大小 ≥ k 如果 bEqual == true。 对于列表的列表,第二个模板返回一个列表的列表,其中结果的元素是原始列表的列表的每个元素中大于 k 的类型组成的列表。结果中会删除所有重复项。 |
获取所有小于或等于 大小 k 的类型 |
template <bool bEqual, size_t k, class List> struct tlist_types>less template <bool bEqual, size_t k, class List> struct tlist_typesdeep_less |
type | using all_types_less_4 = tlist_types_less<true, 4, MyList>::type using all_types_less_5 = tlist_typesdeep_less<false, 5, MyList>::type |
与上一个模板类似,但用于大小小于或等于 k 的类型。 |
将 TypeList 转换为 std::tuple 并返回 [4] |
template <class Src, template<typename...> class Trg> using convert = typename convert_impl<Src, Trg>::type |
using MyTuple = convert<MyList, std::tuple; using MyList = convert<MyTuple, tlist>; |
来自 Peter Dimov [4] 的借鉴 |
您需要记住,所有这些类型、值类型和值都是编译时实体。如果您想在运行时使其可见,则必须添加一些代码来访问它们。
同时也要注意,如果工具返回一个类型、一个值类型和一个值,而您需要的,例如,类型和值,那么最好先获取类型,即 std::integral_constant
,然后使用其成员 'value_type'
和 'value'
,而不是工具的成员,以避免再次使用工具来获取 value
的额外递归。
代码使用 static_assert
来防止使用无效参数。
工具可能会返回一个空的 TypeList,或一个空的 std::integer_sequence
作为合法结果。例如,如果您在不包含此类元素的普通 TypeList 上调用 tlist_all_idx <float, MyList>::type
,您将得到一个空的 std::integer_sequence<size_t>
作为结果。TypeList 也可以将空的 std::integer_sequence
作为一个合法元素。所以,如何区分它们取决于您。
最后一点:tlist_all_idx<element_type, TypeList>::type
返回相关索引的 std::integer_sequence
。tlist_all_idxdeep
返回 std::integer_sequences
的 TypeList。如果您不确定传递的参数是什么,请小心并检查结果类型。
您将在文件 TypeList.hpp 中找到一些处理 std::integer_sequence
的工具。
TypeList 工具箱成员的使用示例在此
#include "type_traits"
#include "TypeList.hpp"
using namespace std;
using namespace typelist;
int main()
{
using MyTypeList = tlist<char, char, int, long, float, double>; // Specialization for your List
constexpr auto sz = MyTypeList::size(); // Number of elements in your List (5)
using type_at_2 = tlist_type_at<2, MyTypeList>::type; // The type is long
constexpr auto bSame = is_same<type_at_2, long >::value; // true
using type_at_12 = tlist_type_at<12, MyTypeList>::type; // Raises static_assert "tlist_type_at: Index out of bounds..."
//.................................................................
return 0;
}
您可以在文件“TypeList.cpp”中找到更多示例。
关注点
要使用 TypeList 工具箱,您需要一个兼容 C++ 14 的编译器。我使用了以下 C++14 特性:
- typename 用于 C++ 11 中的参数包,而不是 class
- 扩展的 constexpr 关键字
- std::integer_sequence
我使用的是 Microsoft Visual Studio 2015 Community Edition Update 1 中的 VC++。它免费且非常好。
TypeList 定义:头部和尾部还是只是参数包?
Andrei Alexandrescu 在 [1] 中为我们提供了 TypeList 的定义:
template <class T, class U>
struct TypeList
{
typedef T Head;
typedef U Tail;
};
当然,用 C++ 14 的术语重写它很容易:
template <typename T, typename... Ts>
struct TypeList
{
using Head = T;
using Tail = TypeList<Ts...>;
};
但有一个问题:这样定义的 TypeList 不能真正为空,它至少必须包含类型 T。如果您尝试实例化 TypeList<>,编译器会抱怨“模板参数不足”。
Andrei 引入了一个特殊的 NullType,它总是位于 TypeList 的末尾。这意味着空列表将始终有一个元素。可变模板允许我们拥有真正空的列表,因为空的参数包是可以的。我想使用一种没有头部和尾部的列表定义。
template <typename... Ts>
struct tlist{};
仍然有一个小问题:使用此定义,您需要一个额外的元函数来获取列表中的元素数量。如果您将此函数命名为 tlist_size
,则必须调用 tlist_sise<MyList>::value
来获取数量。这与 C++ 中使用函数 size()
来获取 STL 容器中的元素数量不同。因此,我决定采用 STL 并从 [3] 中借用了 TypeList 的定义。
template <typename... Ts>
struct tlist
{
using type = tlist;
static constexpr size_t size() noexcept {return sizeof...(Ts);}
};
成员 "type"
是一种语法糖:有时使用 MyList::type
比只使用 MyList
更方便。
请注意,现在空列表是该家族的一个合法成员,因为零参数的参数包是允许的。
请记住,列表中的元素数量是 sizeof...(Ts)
,而不是 sizeof(MyList)
。在我的 PC 上,任何 tlist
的 sizeof(MyList) = 1
。
我们仍然需要 NullType
,但不是作为 TypeList 的强制元素。
通用设计注意事项
TypeList 工具箱中的所有工具都实现为元函数(结构模板)。为了获得结果,我们必须迭代 TypeList 的元素或元素索引。在元编程(编译时)中,迭代是递归。递归必须在某个地方停止。因此,对于每个工具,我们都必须有一个模板定义或声明,以及用于递归和停止条件的特化。
首先,我们必须在工具的前向声明(例如 template <size_t k, class List> struct tlist_index_of;
)或空类定义(例如 template <size_t k, class List> struct tlist_type_at {}
)之间进行选择。我们也可以从一开始就使用可变模板,或者声明或定义一个通用模板,然后使用部分特化来处理参数包。我觉得声明或定义一个通用模板,然后使用部分特化的模板来处理 TypeLists 更方便。
由于头文件 TypeList.hpp 中的工具始终使用模板特化来处理 TypeLists,因此只有当工具作用于错误的类时,编译器才会尝试实例化工具的通用定义。我们会收到错误消息,但消息是否不同取决于我们是使用声明还是定义。
例如,如果我们有一个类 TC
,它不是一个 TypeList,并且我们调用 tlist_type_at<4, TC>::type,
,编译器找不到 TC 的特化,并尝试实例化通用模板。这会失败,我们就会收到一条错误消息。对于声明,错误消息是:“使用未定义的类型,typelist::tlist_type_at<4,TC>'”,而对于空类的定义,它是:“type':不是 'typelist::tlist_type_at<4,TC>' 的成员。”
我更喜欢第一条消息,所以 TypeList.hpp 中我主要在特化之前使用模板的前向声明。也有一些例外。我稍后会讨论它们。
当然,在第一条错误消息之后,您可能会收到更多消息,但最初的消息如上所述。
工具结构模板的成员
下一个问题是我们必须提供哪些成员来访问应用工具的结果。
结果要么是类型,如 tlist_type_at
,要么是数字,如 tlist_index_of
。对于类型,一切都很清楚:类型就是类型,所以模板结构的成员是“type
”。但是如何定义由 tlist_index_of
之类的工具返回的数字的类型和值呢?值必须是编译时常量。有两种方法可以定义这些常量:结构的静态成员/类或枚举值。因为 enum
本身就是一种类型,所以当您需要返回的数字的类型时可能会有问题。所以我选择了静态成员,如下所示:
template <typename... Ts>
struct tlist_index_of<tlist<Ts...>
{
.........................................................
using type = std::integral_constant<size_t, idx>;
using value_type = typename type::value_type;
static constexpr value_type value = type::value;
};
正如您所见,我正在使用 C++ 标准提供的功能。
如果 TypeList 操作的结果是一个类型,则工具的成员就是它本身:“type
”
template <size_t k, typename... Ts>
struct tlist_type_at<tlist<Ts...>>
{
. .........................................
using type = .......;
};
初始条件
有时您需要为递归设置初始条件。例如,tlist_type_at
可能从索引零开始,tlist_split
需要一个空列表来开始打包分割后的列表。为了方便开发人员,我首先编写了这些工具的实现,然后编写了包含这些实现并提供初始类型或值的包装器,例如:
template <size_t idx, typename T class List> // Implementation
struct tlist_index_of_impl ;
......................................
template <typename T, class List>
struct tlist_index_of;
template <typename T, typename H, typename... Ts> // Wrapper of an implementation with initial index zero
struct tlist_index_of<T, tlist<H, Ts...>>
{
using type = typename tlist_index_of_impl<0, T, tlist<H, Ts...>>::type; // index zero is provided
using value_type = typename type::value_type;
enum {value = value};
};
为了简单起见,我没有将实现隐藏在嵌套命名空间(例如 'details')或包装器内部。
如果…:异常情况、static_assert、空列表和空的 std::integer_sequence
您永远不能保证提供给工具的模板参数是正确的类型或值。我们讨论了当您将工具作用于一个不是 typelist 的类时会发生什么,但您也可能在 TypeList 中查找一个不存在的元素。或者搜索列表的索引远远超出了列表中元素的数量。事情就是会发生。
当然,这些是错误。有时您要求编译器执行它无法执行的操作。例如,您要求编译器将 6 个元素的 TypeList 分割成两个 TypeLists:一个包含 12 个元素,另一个包含剩余元素。编译器不知道您真正想要什么,也不知道该怎么做。在这种情况下,可以引发 static_assert
,就像我做的那样。
// Real code is more complicated
template <size_t k, typename... Ts >
struct tlist_split<k, tlist<Ts...>>
{
std::static_assert(k < sizeof... (Ts), "Pivot point is not inside an original list");
std::static_assert(k != 0, "Nothing to split: the pivot point is at zero");
.....................................................................................
};
另一种错误是操作进行,但没有产生预期的结果。假设我们试图获取某个索引处的类型,而该索引超出了 TypeList 的范围。该怎么办?我们可以从索引零开始,递归直到列表末尾。之后,我们可以停止递归并返回某种特殊类型,如 NullType。结果是模棱两可的,因为 NullType 是一种类型,也可能是这个或另一个 typelist 的元素。另一种选择是引发 static_assert
,但这将完全停止编译。我决定遵循 C++ 的例子,即读取超出容器边界会引发运行时断言,并选择第二种选项。
有时一切运行正常,但结果并非如预期。例如,您正在列表中搜索大小为 4 字节的类型,而该列表只有 char 和 double。找不到这样的类型。该工具返回空 TypeList。空 TypeLists、std::integer_sequences
和索引 -1 是这个工具箱中合法的负面结果。
现在我们来讨论一些具体的工具和主题。
NullType 的大小是多少?
有时您需要大小为零的特殊类型,例如,从大小为零的元素开始递归。但是您定义的任何类型都会占用一些内存。C++ 中没有大小为零的类型。
NullType
是特殊的,被声明为一个空类。在我的 PC 上,sizeof(NullType) = 1
,就像 sizeof(tlist<typename... Ts>)
一样。所以我们需要一种方法来解决。我不得不引入一个模板。
class NullType
{};
template <typename T>
struct Sizeof
{
static constexpr size_t value = std::is_same<T, NullType>::value ? 0 : sizeof(T);
};
类型 T 是 TypeList 吗?
为了检查某个编译时类型是否为 TypeList,我编写了一个著名模板的变体(例如,参见 [1],2.7)。
template <typename T>
struct type_to_type
{
using origin = T;
type_to_type() {}
};
template <typename T>
struct is_tlist
{
private:
typedef char (&yes)[1];
typedef char (¬)[2];
template <typename... Ts>
static yes Test(type_to_type<tlist<Ts...>>) {}
static no Test(...) {}
public:
using type = std::integral_constant<bool, sizeof(Test(type_to_type<T>())) == 1>;
using value_type = typename type::value_type;
static constexpr value_type value = type::value;
};
类似的模板用于测试一个类型是否为 std::integer_sequence
。
将 TypeList 分割成多个 TypeLists
此工具将一个 TypeList 分割成多个列表。您将分割列表中的元素数量作为 size_t 数字的参数包提供。该包不包括结果中最后一个列表中元素的数量:最后一个列表包含原始列表中剩余的元素。因此,结果中列表的数量(列表的列表)是参数包的大小加一。您将获得一个列表的列表作为结果(类型)。
显然,参数包中数字的总和必须小于原始列表中的元素数量,因为我们必须为最后一个分割的元素留下非零余数。此外,参数包不能包含零,因为零表示空列表。在列表分割的上下文中,长度为零的列表意味着此时没有分割,因为在从原始列表中移除零个元素后,枢轴点不会改变。
如果不满足这些条件,我们就无法执行分割。该工具必须引发 static_assert
。
为了处理模板参数,我们必须以某种方式组织它们。我们通过将参数插入 std::integer_sequence<size_t, lengths...>
来做到这一点。我们提供元函数 iseq_sum<class ISeq>
和 iseq_index_of<typename T, T nmb, class ISeq>
来检查模板参数。
代码如下。
首先,我们需要一个辅助函数,它将 l 个元素从一个列表移动到第二个列表的末尾,形成一个包含两个列表的列表。
// Helper: move first l elements from one list to the tail of the second list
template <size_t l, class List1, class List2>
struct tlist_move_firsts;
template <typename H, typename... T1s, typename... T2s> // Stop condition, last element is being moved
struct tlist_move_firsts<1, tlist<T1s...>, tlist<H, T2s...>>
{
using type = tlist<tlist<T1s..., H>, tlist<T2s...>>;
};
template <size_t l, typename H, typename... T1s, typename... T2s> // Recursion
struct tlist_move_firsts<l, tlist<T1s...>, tlist<H, T2s...>>
{
static_assert(l > 0, "Number of elements to transfer must be > 0"); // Do not move nothing
using type = typename tlist_move_firsts<l - 1, tlist<T1s..., H>, tlist<T2s...>>::type;
};
接下来,我们必须使用这个辅助函数。思路是取一个空列表,将原始列表的前 k 个元素(根据相应的模板参数请求)移动到空列表中,将新列表添加到结果列表的列表中,并对原始列表的剩余部分重复此操作。
struct tlist_split_impl;
template <size_t l, typename... Lists, typename... Tails> // At the end of the original list
struct tlist_split_impl <std::integer_sequence<size_t, l>, tlist<Lists...>, tlist<Tails...>> // The list before remainders
{
private:
using lst_split_2 = typename tlist_move_firsts<l, tlist<>, tlist<Tails...>>::type; // Fill an empty list with l elements
using first = typename tlist_type_at<0, list_split_2>::type; // Get elements of lst_split_2
using second = typename tlist_type_at<1, lst_split_2>::type;
public:
using type = tlist<Lists..., first, second>; // Add to result
};
template <size_t l, size_t... Ls, typename... Lists, typename... Tails> // Recursion
struct tlist_split_impl<std::integer_sequence<size_t, l, Ls...>, tlist<Lists...>, tlist<Tails...>>
{
private:
using lst_split_2 = typename tlist_move_firsts<l, tlist<>, tlist<Tails...>>::type;
using first = typename tlist_type_at<0, lst_split_2>::type; // We operating on list of two lists
using second = typename tlist_type_at<1, lst_split_2>::type;
using lst_res = tlist<Lists..., first>;
public:
using type = typename tlist_split_impl<std::integer_sequence<size_t, Ls...>, lst_res, second>::type;
};
在这里,我们创建了一个包含空列表和原始列表剩余部分的临时两个列表。我们将原始列表剩余部分的前 l 个元素传输到空列表中。之后,我们将临时列表的第一个列表添加到将要生成的结果列表的列表中,并将临时结果和新的剩余部分传递给递归的下一步。为了获得所需的组件,我们使用了 tlist_type_at
。
现在是时候将所有内容与初始条件(空列表)和参数测试包装在一起了。请注意,您不必费心构建 std::integer_sequence
。您只需要提供一组长度,该工具会处理所有其他事情。
// Wrap the list lengths into integer_sequence, test its parameter pack, and split the original list
template <class List, size_t... Ls> // List is the original list to split, Ls are lengths of components of result
struct tlist_split;
template <typename... Ts, size_t... Ls>
struct tlist_split<tlist<Ts...>, Ls...>
{
// Check sum of lengths; because the last list in the result cannot have zero elements, the sum of lengths of all previous
// elements of resulting list of lists must be less than the number of elements in the original list
using lengths = std::integer_sequence<size_t, Ls...>; // Prepare integer_sequence
using lengths_sum = typename iseq_sum<lengths>::type; // Get sum of lengths
static_assert<lengths_sum::value < tlist<Ts...>::size(), "Split: Check sum of component lengths"); ;
// Test for the first zero in the sequence; if it is here raise static_assert (don't care about other zeros
static_assert(iseq_index_of<size_t, 0, lengths>::value == -1, "Split: No zero lengths allowed");
// Go
using type = typename tlist_split_impl<std::integer_sequence<size_t, Ls...>, tlist<>, tlist<Ts...>>::type;
};
这里有一个应用 tlist_split
的例子。
using lst_msplit = tlist_split<tlist<char, char, int, long, int, float, float, int, double>, 2,2,3>::type;
constexpr auto bLL =
std::is_same<lst_msplit, tlist<tlist<char, char>, tlist<int, long>, tlist<int, float, float>, tlist<int, double>::value;
// bLL == true
请注意,我们引入了一种特殊的 TypeList,即列表的列表:它的所有元素都是列表。有时我们希望对此类列表进行特殊处理。我将在稍后讨论。
从 TypeList 中删除元素
我将通过一个用于从 TypeList 中删除重复项的工具示例来讨论处理列表的列表的一些细节。
在工具箱中有几个用于从 TypeLists 中删除元素的工具。前四个来自 [2]:tlist_erase_type, tlist_erase_at, tlist_erase_all, tlist_erase_dupl
。另外四个是 tlist_pop_front, tlist_pop_back, tlist_erase_firsts, and tlist_erase_lasts
。
tlist_pop_front
和 tlist_pop_back
只是调用 tlist_erase_at
来删除第一个(0)或最后一个元素的索引。tlist_erase_firsts
和 tlist_erase_lasts
最多删除列表前端或尾部的 k 个元素。它们递归地使用 tlist_pop_front
或 tlist_pop_back
。
如果传递给 tlist_erase_at
的索引超出了列表的边界,则会引发 static_assert
。使用 tlist_erase_at
的模板也可能引发此断言。例如,在空列表上调用的 tlist_pop_front
会断言,因为索引零处没有元素(根本没有元素)。
这些工具仅执行此操作:从列表中删除一个或多个元素。但如果存在列表的列表,此功能并不总是足够。
让我们看看上一章中分割列表的结果。有四个 TypeLists,其中两个内部包含重复元素,第一个是 char,第三个是 float。如果您想删除此列表的列表的每个元素(TypeList)中的所有重复项并调用 tlist_erase_dupl
,该工具会将结果的元素(列表)视为整体实体。它永远不会进入这些列表内部。这可能不是您想要的。当然,您可以通过使用 tlist_type_at
单独访问每个元素,并在每个列表上单独调用 tlist_erase_dupl
。这非常麻烦。因此,我决定自动化这个过程和类似情况,并提供一个通用的解决方案,该方案自动为普通 TypeLists 或列表的列表选择正确的工具。对于列表的列表,它将始终查看其内部元素。在其他情况下,我们仍然需要将列表的列表的元素视为整体类型进行处理。例如,删除列表的列表中的重复列表。
这是代码。
首先,我们需要一个工具来删除普通 TypeList 中的重复项(来自 [2] 的借用)。
// Erase all duplicates from the list, borrowed from [2]
template <class List> struct tlist_erase_dupl;
template <> // Specialization for an empty list
struct tlist_erase_dupl<tlist<>>
{
using type = tlist<>;
};
template <typename H, typename... Ts>
struct tlist_erase_dupl<tlist<H, Ts...>>
{
private:
using unique_t = typename tlist_erase_dupl<tlist<Ts...>>::type;
using new_t = typename tlist_erase_type<H, unique_t>::type;
public:
using type = typename tlist_push_front<H, new_t>::type;
};
对于列表的列表,我们将应用此工具来处理列表的列表内部的元素(如果我们想的话)。
// Remove duplicates from each element of the list of list (not borrowed)
template <class List>
struct tlist_erase_dupl_ll
{
using type = tlist<>;
};
template <typename... Hs, typename... ListEls>
struct tlist_erase_dupl_ll<tlist<tlist<Hs...>, ListEls...>>
{
private:
using lst_clean = typename tlist_erase_dupl<tlist<tlist<Hs...>>>::type // Process an individual list
public:
using type = typename tlist_push_front<lst_clean, typename tlist_erase_dupl_ll<tlist<ListEls...>>::type>::type;
};
它只是将 tlist_erase_dupl
应用于列表的列表的每个元素,并将清理后的列表推入结果列表的列表中。
现在让我们组装最终模板。
template <class List>
struct tlist_deeperase_dupl;
template <typename... ListEls>
struct tlist_deeperase_dupl<tlist<ListEls...>>
{
private:
static constexpr bool bLL = is_listoflists<tlist<ListEls...>>::value; // Selector
public:
using type = typename conditional<bLL, typename tlist_erase_dupl_ll<tlist<ListEls...>>::type, // Is called on list of lists
typename tlist_erase_dupl<tlist<ListEls...>>::type>::type; // Is called on general list
};
该工具会自动为普通列表和列表的列表选择正确的实现。它返回一个没有重复项的列表(用于普通 TypeList),或者一个在其每个元素中没有重复项的列表的列表。
有一个问题:部分特化中的 std::conditional
。它在 <type_traits> 中定义为 template <bool B, class T, class F>
。该模板需要一个非类型(bool)和两个类型参数才能实例化。当您在列表的列表上调用 tlist_deeperase_dupl
时,一切正常。但是对于普通列表,编译器找不到 tlist_erase_dupl_ll
的相应部分特化。因此,为了适应普通列表,我不得不使用通用模板的定义,而不是像其他地方那样使用声明。它永远不会被执行,因为对于 std::conditional::type
,tlist_deeperase_dupl
中的开关总是选择正确的偏特化。
那么,如何处理列表的列表的元素作为整体类型,而不是进入内部呢?嗯,您必须显式使用第一个模板。调用 tlist_erase_dupl
,而不是 tlist_deeperase_dupl
。
您可以在上面的表 1 和 TypeList.hpp 中找到其他“深度”工具。
替换 TypeList 中的元素
这个想法很简单:您遍历列表,并在检查点检查第一个元素。如果元素是要替换的类型,则替换它,然后移动到下一个点。下面我展示了 tlist_replace_all
的外观:
// Replace all occurrences of a given type T in TypeList ; R is a replacement for type T
template <typename R, typename T, class List> struct tlist_replace_all;;
template <typename T, typename T> // Specialization for an empty list
struct tlist_replace_all<R, T, tlist<>>
{
using type = tlist<>;
};
template <typename R, typename T, typename... Ts> // Replaces the element T with R
struct tlist_replace_all<R, T, tlist<T, Ts...>>
{
using type = typename tlist_push_front<R, typename tlist_replace_all<R, T, tlist<Ts...>>::type>::type;
};
template <typename R, typename T, typename H, typename... Ts> // Not the element to replace: go farther
struct tlist_replace_all<R, T, tlist<H, Ts...>>
{
using type = typename tlist_push_front<H, typename tlist_replace_all<R, T, tlist<Ts...>>::type>::type;
};
实例化是如何进行的?假设我们有 tlist<char, int, long>
,我们想用 double
替换 int
。作为第一步,我们将 tlist_replace_all
应用于整个列表。列表的前面是 char
类型。它不是 int
,所以我们必须将其返回到列表中。在此步骤中,我们没有列表将此元素推到前面。为了得到它,我们再次将相同的模板应用于列表的剩余部分 tlist<int,long>
。这次我们找到了要替换的类型,所以我们将 double
添加到等待推送到前面的 char
。因为我们仍然没有列表来推送等待的元素,所以我们再次将 tlist_replace_all
应用于 tlist<long>
。
这次,long
被添加到等待推送到前面的类型中。现在我们有一个 TypeList 可以操作:它是一个空列表。空列表的特化被实例化,它返回类型:空列表。现在我们有一个列表可以将等待的元素推送到其中。然后,前一个元素被推送到具有一个元素的列表(long)中,依此类推。最后,我们得到了 tlist<char. double, long>
。
如果列表中不存在要替换的类型的元素,过程也是一样的。我们只会得到原始列表。
如果我们想替换列表的列表中某些类型的元素的所有出现,我们需要一个额外的模板,类似于 tlist_deeperase_dupl
:
template <typename R, typename T, class List>
struct tliat_replace_all_ll
{
using type = tlist<>; // Anything will go, it never be executed, but is requested by std::conditional
};
template <typename R, typename T, typename... Hs, typename... ListEls>
struct tlist_replace_all_ll<R, T, tlist<tlist<Hs...>, ListEls...>>
{
private:
using lst_replaced = typename tlist_replace_all<R, T, tlist<Hs...>>::type;
public:
using type = typename tlist_push_front<lst_replaced, typename tlist_replace_all_ll<R, T, tlist<ListEls...>>::type>::type;
};
// Wrapper
template <typename R, typename T, class List>
struct tlist_deepreplace_all;
template <typename R, typename T, typename... ListEls>
struct tlist_deepreplace_all<R, T, tlist<ListEls...>>
{
private:
static constexpr bool bLL = is_listoflists<tlist<ListEls...>>:value;
public;
using type = std::conditional<bLL, typename tlist_replace_all_ll<R, T, tlist<ListEls...>>::type,
typename tlist_replace_all<R, T, tlist<ListEls...>>::type>::type;
};
还有替换列表中类型的第一次出现,以及替换给定索引处的类型的工具。最后一个工具在接收到超出列表边界的索引时会引发 static_assert
,因为这显然是一个错误。
获取类型在 TypeList 中的所有索引
同样,思路很简单:遍历 TypeList,检查遇到的元素的类型。如果该元素的类型是我们正在寻找的类型,则将元素的索引添加到我们用作索引存储的容器中。
但是这个容器可能是什么?似乎 C++ 14 给我们的唯一选择是 std::integer_sequence
。所以,让我们从空序列开始,并在每次遇到我们正在寻找的类型的元素时,将该元素的索引添加到其中。代码是:
// Get all indexes of the type in the TypeList as a std::integer_sequence
template <size_t k, typename T, class List, class IntSeq> struct tlist_all_idx_impl; // We have to start from the empty IntSeq
template <size_t k, typename T, size_t... Idx> // End of recursion
struct tlist_all_idx_impl<k, T, tlist<>, std::integer_sequence<size_t, Ids...>>
{
using type = std::integer_sequence<size_t, Idx...>;
};
template <size_t k, typename T, typename... Ts, size_t... Idx> // Element T is found; add its index to storage
struct tlist_all_idx_impl<k, T, tlist<T, Ts...>, std::integer_sequence<size_t, Idx...>>
{
using type = typename tlist_all_idx_impl<k + 1, T, tlist<Ts...>, std::integer_sequence<size_t, Idx..., k>>::type;
};
template <size_t k, typename T, typename H, typename... Ts, size_t Idx> // Not the type T
struct tlist_all_idx_implt<k, T, tlist<H, Ts...>, std::integer_sequence<size_t, Idx...>>
{
using type = typename tlist_all_idx_impl<k + 1, T, tlist<Ts...>, std::integer_sequence<size_t, Idx...>>::type;
};
// Now let's hide initial conditions
template <typename T, class List> struct tlist_all_idx;
template <typename T, typename H, typename... Ts> // Start from index zero and empty integer_sequence
struct tlist_all_idx<T, tlist<H, Ts...>>
{
using type = typename tlist_all_idx_impl<0, T, tlist<H, Ts...>, std::integer_sequence<size_t>>::type;
};
此工具将 TypeList 视为整个元素的列表。但对于列表的列表呢?同样,如果我们想获取列表的列表中某个类型元素的索引,我们必须将 tlist_all_idx
应用于列表的列表的每个元素,并将结果打包到 integer_sequences
的 TypeList 中。
// Get all indexes of a type in each element of the list of lists
template <typename T, class Res, class List>
struct tlist_all_idx_ll
{
using type = tlist<std::integer_sequence<size_t>>;// Anything will go, it will be never executed, but is requested by std::conditional
};
template <typename T, typename.. Rs, typename...Hs>
struct tlist_all_idx_ll<T, tlist<Rs...>, tlist<tlist<Hs...>>>
{
private:
using iseq = typename tlist_all_idx_impl<0, T, tlist<Hs...>, std::integer_sequence<size_t>>::type;
public:
using type = tlist<Rs..., iseq>;
};
template <typename T, typename... Rs, typename... Hs, typename... ListEls>
struct tlist_all_idx_ll<T, tlist<Rs...>, tlist<tlist<Hs...>, ListEls...>>
{
private:
using iseq = typename tlist_all_idx_impl<0, T, tlist<Rs...>, tlist<Hs....>>::type;
public:
using type = typename tlist_all_idx_ll<T, tlist<Res..., iseq>, tlist<ListEls...>>::type;
};
// Now wrap it all
template <typename T, class List>
struct tlist_all_deepidx;
template <typename T, typename... ListEls>
struct tlist_all_deepidx<T, tlist<ListEls...>>
{
private:
static constexpr bool bLL = is_listoflists<tlist<ListEls...>>::value;
public:
using type = typename conditional<bLL,
typename tlist_all_idx_ll<T, tlist<>, tlist<ListEls...>>::type,
typename tlist_all_idx_impl<0, T, tlist<ListEls...>, std::integer_sequence<size_t>>::type>::type;
};
同样,如果您想将列表的列表视为普通 TypeList,请使用 tlist_all_idx
。
请注意,通用(深度)工具返回的类型对于普通列表和列表的列表是不同的。对于普通列表,它是 std::integer_sequence
;对于列表的列表,它是 tlist<std::integer_sequence...>
。
到目前为止一切顺利。您获得了索引,但如何使用它们呢?我提供了一套工具来处理 integer_sequence
,所有这些工具都在头文件 TypeList.hpp 中。
template <typename T> struct is_iseq
template <size_t k, class IntSeq> struct iseq_nmb_at
template <typename T, T n, class IntSeq> struct iseq_index_of
template <class IntSeq> struct iseq_sum
(序列中所有元素的总和)template <typename T, T nmb, class IntSeq> struct iseq_remove_nmb
template <typename T, T nmb, class IntSeq> struct iseq_push_back
std::integer_sequence 的工具
如果 std::integer_sequence
的工具需要某个数字作为模板参数,而该数字不是索引,则该数字的类型必须是序列的值类型。我们可能会提供错误的类型。我通过使用实现来完成所有工作,并在包装器中使用大量的 static_assert
来检查错误的类型来解决这个问题。下面是一个例子:
// Get the index of the number
template <size_t idx, typename U, U n, class ISeq> // Implementation
struct iseq_index_of_impl;
template <size_t idx, typename U, U n>
struct iseq_index_of_impl<idx, U, n, std::integer_sequence<U>> // The number id not found
{
using type = std::integral_constant<int, -1>;
};
template <size_t idx, typename U, U n, U... Nmbs>> // The number found
struct iseq_index_of_impl<idx, U, n, std::integer_sequence<U, n, Nmbs...>>
{
using type = std::integral_constant<size_t, n>;
};
template <size_t idx, typename U, U n, U l, U... Nmbs>
struct iseq_index_of_impl<idx, U, n, std::integer_sequence<U, l, Nmbs...>>
{
using type = typename iseq_index_of_impl<idx + 1, U, n, std::integer_sequence<U, Nmbs...>>::type;
};
// Wrapper to supply start index 0, test template arguments, and add members 'value_type' and 'value'
template <<typename U, U n, class ISeq>
struct iseq_index_of
{
private:
static_assert(is_iseq<ISeq>::value, "Supplied class is not integer_sequence"); // integer_sequence is supplied
using data_type = typename ISeq::value_type;
static_assert(std::is_same<data_type. U>::value, "The number must be of the sequence value_type"); // Right ype of number
public:
using type = typename iseq_index_of_impl<idx, data_type, n, ISeq>::type;
using value_type = typename type::value_type;
static constexpr value_type value = type::value;
};
// Call it like this:
using MyISeq = std::integer_sequence<int, 0, -3, 6, 89>; // Looking for number 6 in sequence of int
constexpr auto idx_6 = iseq_index_of<MyISeq::value_type, 6, MyISeq>::value; // idx_6 = 2
当然,您也可以直接使用 iseq_index_of_impl
的特化。区别在于错误消息。如果您为数字提供了错误的类型,例如,为 int 序列提供了 long,那么实现的第一条错误消息是“使用未定义的类型”。iseq_index_of
中的断言将作为第一条错误消息输出您提供给它的文本。
将 TypeList 转换为 std::tuple
TypeList 内部没有值成员。您可以实例化它,但在运行时无法对其进行任何操作(除了获取其大小)。有时,在编译时对列表进行操作后,您可能希望在运行时将其元素(类型)用于其他类,例如将其转换为 std::tuple
。有一个来自 [4] 的模板。
// Use list's parameter pack with some other class
template <class Src, template <typename...> class Trg>
struct convert_impl;
template <template<typename...> class Src, typename... Ts, template <typename... > class Trg
struct convert_impl<Src<Ts...>, Trg>
{
using type = Trg<Ts...>;
};
template <class Src, template <typename...> class Trg>
using convert = typename convert_impl<Src, Trg>::type;
// And below we convert list to tuple
using MyList = tlist<char, int, long, float, double, void*>;
using MyTuple = convert<MyList, std::tuple>;
constexpr auto szMyTuple = tuple_size<MyTuple>::value; // 6
constexpr auto bLT = is_same<MyTuple, std::tuple<char, int, long, float, double, void*>>::value; // true
// Instantiate the tuple somewhere in your executable
MyTuple myTuple;
// Convert MyTuple back to TypeList
using RecList = convert<MyTuple, tlist>;
constexpr auto szList = RecList::size(); // 6
constexpr auto bTL = is_same<RecList, tlist<char, int, long, float, double, void*>>::value; // true
利用 TypeLists 的示例
您可以在 [2] 中找到从 TypeLists 创建类层次结构的示例。它遵循 Andrei Alexandrescu 的路径 [1]。
我将添加一个示例,演示如何使用对象工厂从 TypeList 注册类。这些类是 Ant Colony 算法 [6]:CASTravel, CASETravel, CRASTravel, CBWASTravel, CMMASTravel, CACSTravel。每个类都有一个静态成员函数 Register
。
首先,我们定义 TypeList:
using AlgTypeList = tlist<CACSTravel, CASETravel, CRASTravel, CBWASTravel, CMMASTravel, CACSTravel>:
我们定义一个模板来注册 TypeList 中的类:
template <size_t idx, class List> struct RegFactoryClasses; template <typename H, typename... Ts> // Register last class (might be not all list's types struct RegFactoryClasses<0, tlist<H, Ts...>> { using Result = H; static inline void Fn(void) { Result::RegisterWithFactory(); } }; template <size_t k, typename H, typename... Ts> struct RegFactoryClasses<k, tlist<H, Ts...>> { using Result = typename RegFactoryClasses<k-1, tlist<Ts...>>::Result; static inline void Fn(void) { Result::RegisterWithFactory(); RegFactoryClasses<idx - 1, tlist<H, Ts...>>::Fn(); } };
现在我们定义:
using RegAlgoritms = RegFactoryClasses<AlgTypeList::size() - 1, AlgTypeList>;
在运行时:
RegAlgoritms::Fn();
包里有什么
正如我之前提到的,所有工具模板都在文件 TypeList.hpp 中。该文件还包含用于处理 std::integer_sequences
的模板。它们在一些 TypeList 工具中使用。它包含在 typeListSource.zip 中。
为了使用这些工具,我提供了一个文件 TypeList.cpp。基本上,它定义了几个 TypeLists,对它们应用了一些工具,并使用 std::is_same
从 <type_traits> 中检查结果。
如果您构建并运行此 cpp 文件,您将看不到任何内容。所有处理都在编译时执行。检查也在编译时执行。那么这个 cpp 文件有什么用呢?
我在 TypeListProject.zip 中包含了一个 VS 2015 Community Update 1 的解决方案、项目文件和源文件。您可以轻松地在 VS 2015 中打开该解决方案,构建并编译 TypeList.exe 的debug配置。在调试模式下,您可以通过设置断点和在 Visual Studio 的 Watch 窗口中查看编译时常量来玩转这些工具。
我还将 TypeListProject.zip 中的 doxygen 项目文件 Doxyfile 包含在内,用于生成文档(文件夹 Doc)。如果您想生成 html 文档,请从 Doxygen 网站下载 Doxygen(它是免费的),启动 doxywizard.exe,在向导中加载此文件,并在将 TypeList.hpp 的目录更正为 PC 上的目录后,运行向导。