Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 1 | /* |
2 | * This file is part of the DXX-Rebirth project <http://www.dxx-rebirth.com/>. |
||
3 | * It is copyright by its individual contributors, as recorded in the |
||
4 | * project's Git history. See COPYING.txt at the top level for license |
||
5 | * terms and a link to the Git history. |
||
6 | */ |
||
7 | #pragma once |
||
8 | |||
9 | #include <type_traits> |
||
10 | #include <utility> |
||
11 | |||
12 | namespace detail { |
||
13 | |||
14 | template <typename T> |
||
15 | struct xrange_is_unsigned : std::is_unsigned<T> |
||
16 | { |
||
17 | /* For the general case, delegate to std::is_unsigned. |
||
18 | * xrange requires that the type not be a reference, so there is no |
||
19 | * need to use std::remove_reference<T> here. |
||
20 | */ |
||
21 | }; |
||
22 | |||
23 | template <typename T, T v> |
||
24 | struct xrange_is_unsigned<std::integral_constant<T, v>> : std::is_unsigned<T> |
||
25 | { |
||
26 | /* For the special case that the value is a std::integral_constant, |
||
27 | * examine the underlying integer type. |
||
28 | * std::is_unsigned<std::integral_constant<unsigned, N>> is |
||
29 | * std::false_type, but xrange_is_unsigned should yield |
||
30 | * std::true_type in that case. |
||
31 | */ |
||
32 | }; |
||
33 | |||
34 | template <typename B, typename E> |
||
35 | struct xrange_check_constant_endpoints : std::true_type |
||
36 | { |
||
37 | /* By default, endpoints are not constant and are not checked. */ |
||
38 | }; |
||
39 | |||
40 | template <typename Tb, Tb b, typename Te, Te e> |
||
41 | struct xrange_check_constant_endpoints<std::integral_constant<Tb, b>, std::integral_constant<Te, e>> : std::true_type |
||
42 | { |
||
43 | /* If both endpoints are constant, a static_assert can validate that |
||
44 | * the range is not empty. |
||
45 | */ |
||
46 | static_assert(b < e, "range is always empty due to value of `b` versus value of `e`"); |
||
47 | }; |
||
48 | |||
49 | } |
||
50 | |||
51 | /* For the general case, store a `const`-qualified copy of the value, |
||
52 | * and provide an implicit conversion. |
||
53 | */ |
||
54 | template <typename T, bool begin> |
||
55 | class xrange_endpoint |
||
56 | { |
||
57 | public: |
||
58 | const T value; |
||
59 | constexpr xrange_endpoint(T v) : |
||
60 | value(v) |
||
61 | { |
||
62 | } |
||
63 | constexpr operator T() const |
||
64 | { |
||
65 | return value; |
||
66 | } |
||
67 | }; |
||
68 | |||
69 | /* For the special case that the value is a std::integral_constant, |
||
70 | * inherit from it so that the Empty Base Optimization can apply. |
||
71 | */ |
||
72 | template <typename T, T V, bool begin> |
||
73 | class xrange_endpoint<std::integral_constant<T, V>, begin> : public std::integral_constant<T, V> |
||
74 | { |
||
75 | public: |
||
76 | constexpr xrange_endpoint() = default; |
||
77 | constexpr xrange_endpoint(const std::integral_constant<T, V> &) |
||
78 | { |
||
79 | } |
||
80 | }; |
||
81 | |||
82 | template <typename index_type> |
||
83 | class xrange_iterator |
||
84 | { |
||
85 | index_type m_idx; |
||
86 | public: |
||
87 | constexpr xrange_iterator(const index_type i) : |
||
88 | m_idx(i) |
||
89 | { |
||
90 | } |
||
91 | index_type operator*() const |
||
92 | { |
||
93 | return m_idx; |
||
94 | } |
||
95 | xrange_iterator &operator++() |
||
96 | { |
||
97 | ++ m_idx; |
||
98 | return *this; |
||
99 | } |
||
100 | constexpr bool operator!=(const xrange_iterator &i) const |
||
101 | { |
||
102 | return m_idx != i.m_idx; |
||
103 | } |
||
104 | }; |
||
105 | |||
106 | /* This provides an approximation of the functionality of the Python2 |
||
107 | * xrange object. Python3 renamed it to `range`. The older name is |
||
108 | * kept because it is easier to find with grep. |
||
109 | */ |
||
110 | template <typename index_type, typename B = index_type, typename E = index_type> |
||
111 | class xrange : |
||
112 | public xrange_endpoint<B, true>, |
||
113 | public xrange_endpoint<E, false> |
||
114 | { |
||
115 | using begin_type = xrange_endpoint<B, true>; |
||
116 | using end_type = xrange_endpoint<E, false>; |
||
117 | using iterator = xrange_iterator<index_type>; |
||
118 | /* This static_assert has no message, since the value is always |
||
119 | * true. Use of static_assert forces instantiation of the type, |
||
120 | * which has a static_assert that checks the values and displays a |
||
121 | * message on failure. |
||
122 | */ |
||
123 | static_assert(detail::xrange_check_constant_endpoints<B, E>::value); |
||
124 | static_assert(!std::is_reference<E>::value, "xrange<E> must be a value, not a reference"); |
||
125 | static begin_type init_begin(B b, E e) |
||
126 | { |
||
127 | if constexpr (std::is_convertible<E, B>::value) |
||
128 | { |
||
129 | #ifdef DXX_CONSTANT_TRUE |
||
130 | (DXX_CONSTANT_TRUE(!(b < e)) && (DXX_ALWAYS_ERROR_FUNCTION(xrange_is_always_empty, "begin never less than end"), 0)); |
||
131 | #endif |
||
132 | if (!(b < e)) |
||
133 | return e; |
||
134 | } |
||
135 | else |
||
136 | (void)e; |
||
137 | return b; |
||
138 | } |
||
139 | public: |
||
140 | using range_owns_iterated_storage = std::false_type; |
||
141 | xrange(B b, E e) : |
||
142 | begin_type(init_begin(std::move(b), e)), end_type(std::move(e)) |
||
143 | { |
||
144 | } |
||
145 | xrange(E e) : |
||
146 | begin_type(), end_type(e) |
||
147 | { |
||
148 | static_assert(detail::xrange_is_unsigned<E>::value, "xrange(E) requires unsigned E; use xrange(B, E) if E must be signed"); |
||
149 | } |
||
150 | iterator begin() const |
||
151 | { |
||
152 | return iterator(static_cast<const begin_type &>(*this)); |
||
153 | } |
||
154 | iterator end() const |
||
155 | { |
||
156 | return iterator(static_cast<const end_type &>(*this)); |
||
157 | } |
||
158 | }; |
||
159 | |||
160 | /* Disallow building an `xrange` with a reference to mutable `e` as the |
||
161 | * end term. When `e` is mutable, the loop might change `e` during |
||
162 | * iteration, so using `xrange` could be wrong. If the loop does not |
||
163 | * change `e`, store it in a const qualified variable, which will select |
||
164 | * the next overload down instead. |
||
165 | */ |
||
166 | template <typename Tb, typename Te, typename std::enable_if<!std::is_const<Te>::value, int>::type = 0> |
||
167 | xrange(Tb &&b, Te &e) -> xrange<Tb, Tb, Te &>; // provokes a static_assert failure in the constructor |
||
168 | |||
169 | template <typename Tb, typename Te> |
||
170 | xrange(Tb &&b, Te &&e) -> xrange< |
||
171 | typename std::common_type<Tb, Te>::type, |
||
172 | typename std::remove_const<typename std::remove_reference<Tb>::type>::type, |
||
173 | typename std::remove_const<typename std::remove_reference<Te>::type>::type |
||
174 | >; |
||
175 | |||
176 | template <typename Te> |
||
177 | xrange(const Te &) -> xrange<Te, std::integral_constant<typename std::common_type<Te, unsigned>::type, 0u>>; |
||
178 | |||
179 | template <typename Te> |
||
180 | xrange(Te &) -> xrange< |
||
181 | Te, |
||
182 | std::integral_constant<typename std::common_type<typename std::remove_const<Te>::type, unsigned>::type, 0u>, |
||
183 | Te & |
||
184 | >; |
||
185 | |||
186 | template <typename Te, Te e> |
||
187 | xrange(std::integral_constant<Te, e>) -> xrange<Te, std::integral_constant<Te, Te(0)>, std::integral_constant<Te, e>>; |