/* * 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 template class xrange_endpoint { public: const T value; constexpr xrange_endpoint(T v) : value(v) { } constexpr operator T() const { return value; } }; 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: constexpr xrange_iterator(const index_type i) : m_idx(i) { } index_type operator*() const { return m_idx; } xrange_iterator &operator++() { ++ m_idx; return *this; } constexpr bool operator!=(const xrange_iterator &i) const { return m_idx != i.m_idx; } }; /* 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_extent : public xrange_endpoint, public xrange_endpoint { using begin_type = xrange_endpoint; using end_type = xrange_endpoint; using iterator = xrange_iterator; static begin_type init_begin(B b, E e, std::true_type /* is_same */) { return ( #ifdef DXX_CONSTANT_TRUE /* Compile-time only check. Runtime handles (b > e) * correctly, and it can happen in a correct program. If it * is guaranteed to happen, then the range is always empty, * which likely indicates a bug. */ (DXX_CONSTANT_TRUE(!(b < e)) && (DXX_ALWAYS_ERROR_FUNCTION(xrange_is_always_empty, "begin never less than end"), 0)), #endif b < e ? std::move(b) : e ); } static begin_type init_begin(B b, E, std::false_type /* is_same */) { return begin_type(b); } public: xrange_extent(B b, E e) : begin_type(init_begin(std::move(b), e, std::is_same())), end_type(std::move(e)) { } 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 typename std::enable_if::value, xrange_extent>::type xrange(Tb &&, Te &) = delete; // mutable `e` might change during range template static xrange_extent< typename std::common_type::type, typename std::remove_const::type>::type, typename std::remove_const::type>::type > xrange(Tb &&b, Te &&e) { return {std::forward(b), std::forward(e)}; } /* Disallow building an `xrange` with a default-constructed start term * and a non-unsigned end term. This overload only restricts the * one-argument form of `xrange`. Callers that need a non-unsigned end * term can use the two-argument form to bypass this check. */ template < typename Te, typename Te_rr_rc = typename std::remove_const< typename std::remove_reference::type >::type, typename B = typename std::enable_if< std::is_unsigned::value, std::integral_constant::type, 0u> >::type > static typename std::enable_if::value, xrange_extent>::type xrange(Te &&e) { return {B(), std::forward(e)}; } template typename std::enable_if>::type xrange(std::integral_constant) = delete; // range is invalid or always empty due to value of `e` template , typename E = std::integral_constant> static typename std::enable_if<(Te(0) < e), xrange_extent>::type xrange(std::integral_constant) { return {B(), E()}; }