list序列
list
序列实际上就是曾经介绍的seq
序列的加强版,它的定义如下:
struct list_tag {}; |
可以看到,list
序列是一个有可变模板形参的类模板,它有两个内嵌类型分别是iterator_category_tag
和tag
。其中tag
指示该序列的类型是list_tag
,iterator_category_tag
指示list
序列的迭代器类型是正向迭代器forward_iterator_tag
。除了正向迭代器的tag
,YAMPL还定义了双向和随机访问迭代器。
迭代器名称 | 定义 |
---|---|
正向迭代器 | forward_iterator_tag |
双向迭代器 | bidirectional_iterator_tag |
随机访问迭代器 | random_access_iterator_tag |
正如上一节所说,要完成一个正向迭代器的序列需要实现至少7组基础元函数。接下来我们会逐一的实现这些元函数。
begin_impl
元函数
template <> |
根据上面的代码,让我们来详细的观察begin_impl
的实现。首先可以看到template <> struct begin_impl<list_tag>
是一个template <class Tag> struct begin_impl {};
针对list_tag
的特化版本。它的作用就是让告知编译器在处理tag
为list_tag
的序列时,选用template <> struct begin_impl<list_tag>
这个元函数。回过头来看begin
元函数的实现,
template <class T> |
begin_impl
元函数调用了sequence_tag
来获取序列的tag
,从而让编译器能够正确选择begin_impl
的版本。sequence_tag
的实现很简单,就是获取类型的tag
并返回。
template <class T> |
接着观察begin_impl<typename sequence_tag<T>::type>::template apply<T>
的apply<T>
,我们发现在编译器选择了正确的begin_impl
后,真正发挥作用的是内嵌元函数template <class T> struct apply
,它的任务是处理begin
传入的模板参数T
,并且返回第1个迭代器。
template <template <class...> class T, class... Args> |
apply
的实现并不复杂,需要注意的是返回迭代器中模板实参的含义。第一个实参T<Args...>
是记录当前迭代器所代表的元素以及该元素之后所有元素的序列,比方说现在有一个迭代器的第一个实参为list<int, char, double>
,那么它的下一个迭代器的第一个实参应该是list<char, double>
。第二个实参integral_const<int, 0>
是用来记录当前迭代器在序列中的位置,因为begin
返回的是序列的第一个迭代器,所有其位置应该是0。最后的实参T<Args...>
对整个序列的记录,由于是首个迭代器所以这个实参看起来和第一个实参相同。另外需要注意的是在正向迭代器中,第三个实参并没有什么作用,之所以给出了这个定义是因为YAMPL还为list
实现了一个双向迭代器的版本。
end_impl
元函数
template <> |
end_impl
的实现和begin_impl
基本相同,唯一的区别是内嵌元函数apply
返回的迭代器的定义不同,它将返回序列最后一个元素之后的迭代器。该迭代器的第一个实参为T<>
,这说明该迭代器已经没有代表的元素了。第二个实参integral_const<int, sizeof...(Args)>
同样表示当前迭代器在序列的位置。最后的实参T<Args...>
还是对整个序列的记录。
size_impl
元函数
template <> |
size_impl
的内嵌元函数apply
返回的是序列中元素的数量integral_const<int, sizeof...(Args)>
。
clear_impl
元函数
template <> |
clear_impl
的内嵌元函数apply
返回的是空序列T<>
。
push_back_impl
元函数
template <> |
push_back_impl
的内嵌元函数apply
和之前我们看到的apply
元函数有一些区别,它需要两个模板参数,这也正是push_back
元函数所需的模板参数。
template <class T, class U> |
其中实参T
是序列本身,实参U是需要插入序列的元素。最终apply
返回的是插入新元素之后的序列T<Args..., U>
。
next_impl
元函数
template <> |
next_impl
的内嵌元函数apply
是一个针对迭代器的元函数,之前我们看到的无论是begin_impl
还是push_back_impl
的apply
元函数都是针对序列本身的。这一点从next
的定义中也能看出点端倪。
template <class T> |
我们发现,next_impl
并没有调用sequence_tag
获取序列tag
,而是采用iterator_sequence_tag
元函数获取迭代器所属序列的tag
,所以这里next
元函数操作的主体对象是迭代器而不是序列。
回头来看next_impl
中apply
的代码,可以看到apply
有两个特化版本,首先当模板实参为iterator<T<U, Args...>, N, B>
时,说明该迭代器不是倒数第二个迭代器,那么apply
的返回结果应该是下个迭代器iterator<T<Args...>, typename N::next, B>
。请注意,因为我们知道模板形参N
是一个integral_const
类型,所以可以直接使用N::next
获取它的下一个整型包装类。
接下来当模板实参为iterator<T<U>, N, B>
时,说明它是序列中倒数第二个迭代器。这时apply
应该与end
元函数返回一个相同的迭代器iterator<T<>, typename N::next, B>
。
deref_impl
元函数
template <> |
deref_impl
和next_impl
一样也是针对迭代器的元函数,它对迭代器进行解引用操作,随后可以获得元素本身。观察deref_impl
的内嵌apply
元函数,它也有两个特化版本。当其实参的迭代器为iterator<T<U, Args...>, N, B>
时,说明它不是最后一个迭代器,于是返回当前元素U
。当实参的迭代器为iterator<T<>, N, B>
时,说明这是最后一个迭代器,它不包含任何元素,所以返回none
。这里的none
是YAMPL专门为类似这种情况定义的类型,用来表示没有意义的结果,具体定义如下:
struct none_tag {}; |
list
序列和迭代器的基本用法
熟悉STL的读者一定对迭代器的使用了如指掌,因为STL中关于容器的大部分函数都依赖迭代器。比如从std::list
的容器中删除一个元素就需要使用迭代器指明元素的位置。
std::list<int> mylist{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
模板元编程中序列和迭代器的使用方法和STL的迭代器比较类似,只是代码的写法上有些不同,总的来说还算比较容易掌握。以下是一段调用并验证yampl::list
基本元函数的代码:
std::cout << std::boolalpha; |
在上面的代码中,首先定义了一个list
序列my_list
,序列共有5个元素,它们从int_<0>
递增至int<4>
。使用size
元函数可以获取my_list
中元素个数,这里返回的是int_<5>
,通过::value
可以获取整型数字5,所以第一句std::cout
输出结果为:
my_list size = 5 |
接着,代码调用begin
元函数返回了序列my_list
的第一个迭代器it0
,可以预见到这个迭代器解引用后的元素就是int_<0>
。为了证明这一点,使用元函数deref
可以对it0
解引用并获得结果elem0
,再使用std::is_same_v<elem0, int_<0>>
验证类型是否相同,输出结果为:
elem0 == int_<0> : true |
为了获取下一个迭代器,可以使用元函数next
,这样可以获取迭代器it1
。通过同样的方式我们可以获取it2
到it4
,并且对它们的类型进行判断:
elem1 == int_<1> : true |
it4
的下一个迭代器是my_list
中最后一个迭代器,应该与end
元函数返回的结果相同。我们同样可以通过std::is_same_v<typename next<it4>::type, typename end<my_list>::type>
来验证这个结论:
next<it4>::type == end<mylist>::type : true |
最后,代码中使用clear
元函数返回一个空list
序列,并且调用push_back
将int_<5>
插入到序列之中并获得序列my_list2
。用同样的方法再次验证push_back
和clear
元函数的正确性,得到输出结果:
my_list2 == list<int_<5> : true |