不同编译器实现代码可能不完全一样,本文中代码均来自msvc.
迭代器分类标签 std::random_access_iterator_tag
随机存取标签,如下代码,这是一个空结构体,什么也不做,仅用来标识随机存取迭代器,除随机存取标识外还有其他类别的标识。
struct random_access_iterator_tag : bidirectional_iterator_tag {};
用途
这个标签有什么用呢? 不用可不可以?
迭代器分类标签用来进行编译期间的算法选择。这有两层意思:
- 这个标签用来做针对迭代器的算法选择
- 算法选择发生在编译阶段
常用迭代器分类标签
首先认识一下常用容器的迭代器的分类标签,
vector | struct std::random_access_iterator_tag |
list | struct std::bidirectional_iterator_tag |
deque | struct std::random_access_iterator_tag |
array | struct std::random_access_iterator_tag |
map | struct std::bidirectional_iterator_tag |
unordered_map | struct std::bidirectional_iterator_tag |
set | struct std::bidirectional_iterator_tag |
unordered_set | struct std::bidirectional_iterator_tag |
获取代码如下,
#include <iterator>
#include <vector>
#include <stdio.h>
#include <list>
#include <deque>
#include <array>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
using namespace std;
int main()
{
iterator_traits<vector<int>::iterator>::iterator_category cati;
iterator_traits<list<char>::iterator>::iterator_category catlc;
iterator_traits<deque<int>::iterator>::iterator_category catqi;
iterator_traits<array<int,5>::iterator>::iterator_category catai;
iterator_traits<map<int, int>::iterator>::iterator_category catmii;
iterator_traits<unordered_map<int,int>::iterator>::iterator_category catumii;
iterator_traits<set<int, int>::iterator>::iterator_category catsii;
iterator_traits<unordered_set<int, int>::iterator>::iterator_category catusii;
printf("vector|%s\n", typeid (cati).name());
printf("list|%s\n", typeid (catlc).name());
printf("deque|%s\n", typeid (catqi).name());
printf("array|%s\n", typeid (catai).name());
printf("map|%s\n", typeid (catmii).name());
printf("unordered_map|%s\n", typeid (catumii).name());
printf("set|%s\n", typeid (catsii).name());
printf("unordered_set|%s\n", typeid (catusii).name());
return 0;
}
算法选择
然后我们看一下一个算法选择的例子,以std::distance为例子,distance用于获取从first到last的“行程”。
template< class InputIt >
typename std::iterator_traits<InputIt>::difference_type
distance( InputIt first, InputIt last );
我们知道对于vector或者数组这样的随机存取容器,获取两个位置之间的行程非常简单,且时间复杂度为常数,因为元素是
连续存储的。而对于list和map等容器,行程的获取就略加复杂。因此我们希望针对不同容器可以有不同的求解算法,以下是代码实现:
template <class _InIt>
_NODISCARD _CONSTEXPR17 _Iter_diff_t<_InIt> distance(_InIt _First, _InIt _Last) {
if constexpr (_Is_random_iter_v<_InIt>) {
return _Last - _First; // assume the iterator will do debug checking
} else {
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
_Iter_diff_t<_InIt> _Off = 0;
for (; _UFirst != _ULast; ++_UFirst) {
++_Off;
}
return _Off;
}
}
从上边的代码可以看出,对于随机存取容器return _Last - _First;
这一条表达式就可以了,而对于其他容器则需要一个一个的查找对比。
怎么将选择实现在编译阶段
编译阶段的算法选取通过if constexpr
表达式实现,与普通的if else想比,后者的判断发生在程序执行阶段,而前者的判断发生在
编译阶段。
编译期间算法选择的另外一种方式
if constexpr
语句是c++17之后出现的新特性,在此之前使用如下方式实现同样功能。
template <class _InIt>
_CONSTEXPR17 _Iter_diff_t<_InIt> _Distance1(_InIt _First, _InIt _Last, input_iterator_tag) {
// return distance between iterators; input
_Adl_verify_range(_First, _Last);
auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
_Iter_diff_t<_InIt> _Off = 0;
for (; _UFirst != _ULast; ++_UFirst) {
++_Off;
}
return _Off;
}
template <class _RanIt>
_CONSTEXPR17 _Iter_diff_t<_RanIt> _Distance1(_RanIt _First, _RanIt _Last, random_access_iterator_tag) {
// return distance between iterators; random-access
return _Last - _First;
}
template <class _InIt>
_NODISCARD _CONSTEXPR17 _Iter_diff_t<_InIt> distance(_InIt _First, _InIt _Last) {
return _Distance1(_First, _Last, _Iter_cat_t<_InIt>());
}
另外可以看到,msvc中通过宏_HAS_IF_CONSTEXPR
来分割了两种方法
// FUNCTION TEMPLATE distance
#if _HAS_IF_CONSTEXPR
#else // ^^^ _HAS_IF_CONSTEXPR / !_HAS_IF_CONSTEXPR vvv
#endif // _HAS_IF_CONSTEXPR