/* * This file is part of the DXX-Rebirth project . * It is copyright by its individual contributors, as recorded in the * project's Git history. See COPYING.txt at the top level for license * terms and a link to the Git history. */ #pragma once #include #include #include /* These could be empty tag types, but defining them from * std::integral_constant allows the deduction guide for * std::integral_constant<$integer_type, V> to handle them. If these * were done as tag types, a separate deduction guide would be required. */ using xrange_ascending = std::true_type; using xrange_descending = std::false_type; namespace detail { template struct xrange_is_unsigned : std::is_unsigned { /* For the general case, delegate to std::is_unsigned. * xrange requires that the type not be a reference, so there is no * need to use std::remove_reference here. */ }; template struct xrange_is_unsigned> : std::is_unsigned { /* For the special case that the value is a std::integral_constant, * examine the underlying integer type. * std::is_unsigned> is * std::false_type, but xrange_is_unsigned should yield * std::true_type in that case. */ }; template struct xrange_check_constant_endpoints : std::true_type { /* By default, endpoints are not constant and are not checked. */ }; template struct xrange_check_constant_endpoints, std::integral_constant, xrange_ascending> : std::true_type { /* If both endpoints are constant, a static_assert can validate that * the range is not empty. */ static_assert(b < e, "range is always empty due to value of `b` versus value of `e`"); }; template struct xrange_check_constant_endpoints, std::integral_constant, xrange_descending> : std::true_type { static_assert(e < b, "range is always empty due to value of `b` versus value of `e`"); }; template struct xrange_check_constant_endpoints, std::integral_constant, std::integral_constant> : xrange_check_constant_endpoints< std::integral_constant, std::integral_constant, typename std::conditional<(step > 0), xrange_ascending, xrange_descending>::type > { /* The implementation of xrange_iterator::operator++ moves the * current position by exactly the step value, every time. If * std::abs(step) is greater than 1, then some values will be * stepped over. Assert that skipping values will not cause * operator++ to skip over the end value. If it did, the range * would continue until wraparound terminated it, the program * abandoned reading the range, or the program crashed due to out of * range values. */ static_assert((step > 0 ? ((e - b) % step) : ((b - e) % -step)) == 0, "step size will overstep end value"); }; } /* For the general case, store a `const`-qualified copy of the value, * and provide an implicit conversion. */ template class xrange_endpoint { public: using value_type = T; const value_type value; constexpr xrange_endpoint(value_type v) : value(std::move(v)) { } constexpr operator value_type() const { return value; } }; /* For the special case that the value is a std::integral_constant, * inherit from it so that the Empty Base Optimization can apply. */ template class xrange_endpoint, begin> : public std::integral_constant { public: constexpr xrange_endpoint() = default; constexpr xrange_endpoint(const std::integral_constant &) { } }; template class xrange_iterator { index_type m_idx; public: using difference_type = std::ptrdiff_t; using iterator_category = std::forward_iterator_tag; using value_type = index_type; using pointer = value_type *; using reference = value_type &; constexpr xrange_iterator(const index_type i) : m_idx(i) { } index_type operator*() const { return m_idx; } xrange_iterator &operator++() { if constexpr (std::is_same::value) ++ m_idx; else if constexpr (std::is_same::value) -- m_idx; else m_idx += step_type::value; return *this; } [[nodiscard]] constexpr bool operator==(const xrange_iterator &i) const = default; }; /* This provides an approximation of the functionality of the Python2 * xrange object. Python3 renamed it to `range`. The older name is * kept because it is easier to find with grep. */ template class xrange : protected xrange_endpoint, protected xrange_endpoint { protected: using begin_type = xrange_endpoint; using end_type = xrange_endpoint; using iterator = xrange_iterator; /* This static_assert has no message, since the value is always * true. Use of static_assert forces instantiation of the type, * which has a static_assert that checks the values and displays a * message on failure. */ static_assert(detail::xrange_check_constant_endpoints::value); static_assert(!std::is_reference::value, "xrange must be a value, not a reference"); static constexpr B init_begin(B b, E e) { if constexpr (std::is_convertible::value) { if constexpr (std::is_same::value || (!std::is_same::value && step_type::value > 0)) { #ifdef DXX_CONSTANT_TRUE (DXX_CONSTANT_TRUE(!(b < e)) && (DXX_ALWAYS_ERROR_FUNCTION(xrange_is_always_empty, "begin never less than end"), 0)); #endif if (!(b < e)) return e; } else { #ifdef DXX_CONSTANT_TRUE (DXX_CONSTANT_TRUE(!(e < b)) && (DXX_ALWAYS_ERROR_FUNCTION(xrange_is_always_empty, "end never less than begin"), 0)); #endif if (!(e < b)) return e; } } else (void)e; return b; } public: using end_type::value; using end_type::operator typename end_type::value_type; using range_owns_iterated_storage = std::false_type; /* If the endpoints are both integral constants, then they will have a * default constructor that this explicitly defaulted default constructor * can call. They will have no non-static data members, so their default * constructors will produce reasonable values. * * Otherwise, the endpoints will not have a default constructor, and this * default constructor will fail to compile. That is desirable, since such * an endpoint would have a non-static data member that would not be * initialized by a default constructor. */ constexpr xrange() = default; constexpr xrange(B b, E e) : begin_type(init_begin(std::move(b), e)), end_type(std::move(e)) { } /* Allow, but ignore, a third argument with the step size. The * argument must be here for class template argument deduction to * work. step_type is included in the class template argument list, * and so does not need to be recorded in the object. */ template constexpr xrange(B b, E e, std::integral_constant) : xrange(std::move(b), std::move(e)) { } constexpr xrange(E e) : begin_type(), end_type(std::move(e)) { static_assert(detail::xrange_is_unsigned::value, "xrange(E) requires unsigned E; use xrange(B, E) if E must be signed"); } iterator begin() const { return iterator(static_cast(*this)); } iterator end() const { return iterator(static_cast(*this)); } }; /* Disallow building an `xrange` with a reference to mutable `e` as the * end term. When `e` is mutable, the loop might change `e` during * iteration, so using `xrange` could be wrong. If the loop does not * change `e`, store it in a const qualified variable, which will select * the next overload down instead. */ template requires(!std::is_const::value) xrange(Tb &&b, Te &e) -> xrange; // provokes a static_assert failure in the constructor template xrange(Tb &&b, Te &&e) -> xrange< typename std::common_type::type, typename std::remove_const::type>::type, typename std::remove_const::type>::type >; template xrange(Tb &&b, Te &&e, std::integral_constant) -> xrange< typename std::common_type::type, typename std::remove_const::type>::type, typename std::remove_const::type>::type, std::integral_constant >; template xrange(const Te &) -> xrange::type, 0u>>; template xrange(Te &) -> xrange< Te, std::integral_constant::type, unsigned>::type, 0u>, Te & >; template xrange(std::integral_constant) -> xrange, std::integral_constant>;