-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathzip_two.hpp
More file actions
155 lines (126 loc) · 4.43 KB
/
zip_two.hpp
File metadata and controls
155 lines (126 loc) · 4.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#pragma once
#include <type_traits>
#include <vector>
/*
This file demonstrates how you can perform parallel iteration of two types.
*/
namespace c9 {
/* This is an example of how you can change the access type for a given type.
Because are example uses std::vector, and std::vector has a specialisation
for `bool` that returns a special iterator that can only be dereferenced by
value, then we need to make sure that, in that case, a value type is
returned rather than a reference. */
template <typename Iter>
using select_access_type_for = std::conditional_t<
std::is_same_v<Iter, std::vector<bool>::iterator> ||
std::is_same_v<Iter, std::vector<bool>::const_iterator>,
typename Iter::value_type,
typename Iter::reference
>;
/* This is an iterator-like object. It's not really a proper iterator, but
it does satisfy the requirements needed for range-for. */
template <typename Iter1, typename Iter2>
class zip_iterator
{
public:
using value_type = std::pair<
select_access_type_for<Iter1>,
select_access_type_for<Iter2>
>;
zip_iterator() = delete;
zip_iterator(Iter1 iter_1_begin, Iter2 iter_2_begin)
: m_iter_1_begin {iter_1_begin}
, m_iter_2_begin {iter_2_begin}
{
}
auto operator++() -> zip_iterator&
{
++m_iter_1_begin;
++m_iter_2_begin;
return *this;
}
auto operator++(int) -> zip_iterator
{
auto tmp = *this;
++*this;
return tmp;
}
auto operator!=(zip_iterator const & other) const
{
return !(*this == other);
}
/* The comparison we need to make in order to detect when iteration should
end is when any of the iterators equal their end position. From a
semantics point of view, equality in this sense is not really accurate;
you would expect, ordinarily, that the test would be the aggregate
equivalence of it's members. However, if the things being iterated over
have different sizes, both iterators will never reach the end at the
same time. Therefore we need to return true as soon as any of the
iterators are at their end position, terminating the iteration loop.
*/
auto operator==(zip_iterator const & other) const
{
return
m_iter_1_begin == other.m_iter_1_begin ||
m_iter_2_begin == other.m_iter_2_begin;
}
auto operator*() -> value_type
{
return value_type{*m_iter_1_begin, *m_iter_2_begin};
}
private:
Iter1 m_iter_1_begin;
Iter2 m_iter_2_begin;
};
/* We need to select the correct iterator for the passed in types. If one of
the types is a `std::vector<int> const &` then we need to make sure we use
it's `const_iterator, rather than the normal iterator.
std::decay is needed because T is a reference, and is not a complete type.
Using decay will give us the fundamental type and allows to access it's type
definitions */
template <typename T>
using select_iterator_for = std::conditional_t<
std::is_const_v<std::remove_reference_t<T>>,
typename std::decay_t<T>::const_iterator,
typename std::decay_t<T>::iterator>;
/* Class that is called upon to do the zipping. It contains the necessary
functions to satisfy range-for, by providing both begin and end functions.
*/
template <typename T, typename U>
class zipper
{
public:
using Iter1 = select_iterator_for<T>;
using Iter2 = select_iterator_for<U>;
using zip_type = zip_iterator<Iter1, Iter2>;
template <typename V, typename W>
zipper(V && a, W && b)
: m_a{a}
, m_b{b}
{
}
auto begin() -> zip_type
{
return zip_type{std::begin(m_a), std::begin(m_b)};
}
auto end() -> zip_type
{
return zip_type{std::end(m_a), std::end(m_b)};
}
private:
T m_a;
U m_b;
};
/* Utility method that used at the user's call site. The reason for this is
that it allows the types of what's passed in to be deduced. Otherwise if
you were to use the class directly, you would need to provide template
arguments.
We also need to take a universal reference -- not because we want to take
both lvalues and rvalues (it doesn't make sense to take an rvalue) but
because we don't know whether the type is const or non-const. */
template <typename T, typename U>
auto zip(T && t, U && u)
{
return zipper<T, U>{std::forward<T>(t), std::forward<U>(u)};
}
} // namespace c9