65.9K
CodeProject 正在变化。 阅读更多。
Home

使用可变模板实现 TypeLists 和 TypeList 工具箱

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (15投票s)

2016 年 2 月 10 日

CPOL

34分钟阅读

viewsIcon

27923

downloadIcon

821

本文介绍使用编译时类型列表进行元编程。

引言

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”中有一个包含所有工具的表格:>

表 1. TypeList 工具
工具 定义 成员 用法 注意
TypeList 定义
[3]
template <typename... Ts> tlist

type,
size()

using MyList =
tlist<char, char, int, long, long, int, float, double>;

constexpr auto szMyList = MyList::size();

type 是 TypeList 本身
size() 返回列表中的元素数量
检查列表是否为空 template <class List> struct tlist_islistempty

type,
value_type
value

constexpr auto bEmpty = tlist_islistempty<<MyList>::value;

type std::true_type std::false_type
value_type 是 bool
value 如果列表为空则为 true

检查类型是否为 TypeList template <class List> struct is_list type,
value_type
value
constexpr auto bIsList = is_list<<MyList>::value;

type std::integral_constant<bool, value>,
value_type 是 bool
value 如果类型是 TypeList 则为 true

检查列表是否为列表的列表 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>
struct tlist_type_at

type

using type_at_3 =
tlist_type_at<3, MyList>::type;

type 是索引 idx 处列表元素的类型。如果索引超出列表边界,则会引发 static_assert

在 TypeList 中搜索类型 T 的第一次出现

template <typename T, class List>
struct tlist_index_of

type,
value_type
value

constexpr auto idx =
tlist_index_of<<double, MyList>::value;

typestd::integral_constant<int, value>
value_type 是 int
value 是类型 T 在 TypeList 中第一次出现的位置索引,如果找不到该类型则为 -1

在 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>
struct tlist_front

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_sequencetlist_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 上,任何 tlistsizeof(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 (&not)[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_fronttlist_pop_back 只是调用 tlist_erase_at 来删除第一个(0)或最后一个元素的索引。tlist_erase_firsts tlist_erase_lasts 最多删除列表前端或尾部的 k 个元素。它们递归地使用 tlist_pop_fronttlist_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::typetlist_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.exedebug配置。在调试模式下,您可以通过设置断点和在 Visual Studio 的 Watch 窗口中查看编译时常量来玩转这些工具。

我还将 TypeListProject.zip 中的 doxygen 项目文件 Doxyfile 包含在内,用于生成文档(文件夹 Doc)。如果您想生成 html 文档,请从 Doxygen 网站下载 Doxygen(它是免费的),启动 doxywizard.exe,在向导中加载此文件,并在将 TypeList.hpp 的目录更正为 PC 上的目录后,运行向导。

© . All rights reserved.