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>>; |