4071cd0cbf
The standard type imposes some additional requirements that are not necessary here, but using this concept allows standard containers to be classified correctly without specific overrides.
304 lines
10 KiB
C++
304 lines
10 KiB
C++
/*
|
|
* This file is part of the DXX-Rebirth project <http://www.dxx-rebirth.com/>.
|
|
* 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 <type_traits>
|
|
#include <iterator>
|
|
#include <ranges>
|
|
#include <utility>
|
|
|
|
/* These could be empty tag types, but defining them from
|
|
* std::integral_constant<bool, V> 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 <typename T>
|
|
struct xrange_is_unsigned : std::is_unsigned<T>
|
|
{
|
|
/* 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<T> here.
|
|
*/
|
|
};
|
|
|
|
template <typename T, T v>
|
|
struct xrange_is_unsigned<std::integral_constant<T, v>> : std::is_unsigned<T>
|
|
{
|
|
/* For the special case that the value is a std::integral_constant,
|
|
* examine the underlying integer type.
|
|
* std::is_unsigned<std::integral_constant<unsigned, N>> is
|
|
* std::false_type, but xrange_is_unsigned should yield
|
|
* std::true_type in that case.
|
|
*/
|
|
};
|
|
|
|
template <typename B, typename E, typename step_type>
|
|
struct xrange_check_constant_endpoints : std::true_type
|
|
{
|
|
/* By default, endpoints are not constant and are not checked. */
|
|
};
|
|
|
|
template <typename Tb, Tb b, typename Te, Te e>
|
|
struct xrange_check_constant_endpoints<std::integral_constant<Tb, b>, std::integral_constant<Te, e>, 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 <typename Tb, Tb b, typename Te, Te e>
|
|
struct xrange_check_constant_endpoints<std::integral_constant<Tb, b>, std::integral_constant<Te, e>, xrange_descending> : std::true_type
|
|
{
|
|
static_assert(e < b, "range is always empty due to value of `b` versus value of `e`");
|
|
};
|
|
|
|
template <typename Tb, Tb b, typename Te, Te e, typename Tstep, Tstep step>
|
|
struct xrange_check_constant_endpoints<std::integral_constant<Tb, b>, std::integral_constant<Te, e>, std::integral_constant<Tstep, step>> :
|
|
xrange_check_constant_endpoints<
|
|
std::integral_constant<Tb, b>,
|
|
std::integral_constant<Te, e>,
|
|
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 copy of the value,
|
|
* and provide an implicit conversion.
|
|
*/
|
|
template <typename T, bool begin>
|
|
class xrange_endpoint
|
|
{
|
|
public:
|
|
using value_type = T;
|
|
/* The value is never mutated, but must be mutable to satisfy
|
|
* std::ranges::range<xrange<...>>.
|
|
*/
|
|
value_type value{};
|
|
constexpr xrange_endpoint() = default;
|
|
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 <typename T, T V, bool begin>
|
|
class xrange_endpoint<std::integral_constant<T, V>, begin> : public std::integral_constant<T, V>
|
|
{
|
|
public:
|
|
constexpr xrange_endpoint() = default;
|
|
constexpr xrange_endpoint(const std::integral_constant<T, V> &)
|
|
{
|
|
}
|
|
};
|
|
|
|
template <typename index_type, typename step_type>
|
|
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() = default; // default constructible required by std::semiregular
|
|
constexpr xrange_iterator(const index_type i) :
|
|
m_idx(i)
|
|
{
|
|
}
|
|
/* This is a temporary constructor to facilitate conversion to sentinel
|
|
* usage in calling algorithms.
|
|
*/
|
|
template <typename end_index_type>
|
|
constexpr xrange_iterator(xrange_endpoint<end_index_type, false> i) :
|
|
m_idx(i)
|
|
{
|
|
}
|
|
index_type operator*() const
|
|
{
|
|
return m_idx;
|
|
}
|
|
xrange_iterator &operator++()
|
|
{
|
|
if constexpr (std::is_same<step_type, xrange_ascending>::value)
|
|
++ m_idx;
|
|
else if constexpr (std::is_same<step_type, xrange_descending>::value)
|
|
-- m_idx;
|
|
else
|
|
m_idx += step_type::value;
|
|
return *this;
|
|
}
|
|
xrange_iterator operator++(int)
|
|
{
|
|
auto r = *this;
|
|
++ *this;
|
|
return r;
|
|
}
|
|
[[nodiscard]]
|
|
constexpr bool operator==(const xrange_iterator &i) const = default;
|
|
template <typename end_index_type>
|
|
[[nodiscard]]
|
|
constexpr bool operator==(const xrange_endpoint<end_index_type, false> &i) const
|
|
{
|
|
return m_idx == i.value;
|
|
}
|
|
};
|
|
|
|
/* 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 <typename index_type, typename B = index_type, typename E = index_type, typename step_type = xrange_ascending>
|
|
class xrange :
|
|
protected xrange_endpoint<B, true>,
|
|
protected xrange_endpoint<E, false>
|
|
{
|
|
protected:
|
|
using begin_type = xrange_endpoint<B, true>;
|
|
using end_type = xrange_endpoint<E, false>;
|
|
using iterator = xrange_iterator<index_type, step_type>;
|
|
/* 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<B, E, step_type>::value);
|
|
static_assert(!std::is_reference<E>::value, "xrange<E> must be a value, not a reference");
|
|
static constexpr B init_begin(B b, E e)
|
|
{
|
|
if constexpr (std::is_convertible<E, B>::value)
|
|
{
|
|
if constexpr (std::is_same<step_type, xrange_ascending>::value || (!std::is_same<step_type, xrange_descending>::value && step_type::value > 0))
|
|
{
|
|
#ifdef DXX_CONSTANT_TRUE
|
|
(DXX_CONSTANT_TRUE(!(b < e)) && (DXX_ALWAYS_ERROR_FUNCTION("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("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;
|
|
/* 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 <typename T, T step>
|
|
constexpr xrange(B b, E e, std::integral_constant<T, step>) :
|
|
xrange(std::move(b), std::move(e))
|
|
{
|
|
}
|
|
constexpr xrange(E e) :
|
|
begin_type(), end_type(std::move(e))
|
|
{
|
|
static_assert(detail::xrange_is_unsigned<E>::value, "xrange(E) requires unsigned E; use xrange(B, E) if E must be signed");
|
|
}
|
|
iterator begin() const
|
|
{
|
|
return iterator(static_cast<const begin_type &>(*this));
|
|
}
|
|
end_type end() const
|
|
{
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
template <typename index_type, typename B, typename E, typename step_type>
|
|
inline constexpr bool std::ranges::enable_borrowed_range<xrange<index_type, B, E, step_type>> = true;
|
|
|
|
/* 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 Tb, typename Te>
|
|
requires(!std::is_const<Te>::value)
|
|
xrange(Tb &&b, Te &e) -> xrange<Tb, Tb, Te &>; // provokes a static_assert failure in the constructor
|
|
|
|
template <typename Tb, typename Te>
|
|
xrange(Tb &&b, Te &&e) -> xrange<
|
|
typename std::common_type<Tb, Te>::type,
|
|
typename std::remove_const<typename std::remove_reference<Tb>::type>::type,
|
|
typename std::remove_const<typename std::remove_reference<Te>::type>::type
|
|
>;
|
|
|
|
template <typename Tb, typename Te, typename Tstep, Tstep step>
|
|
xrange(Tb &&b, Te &&e, std::integral_constant<Tstep, step>) -> xrange<
|
|
typename std::common_type<Tb, Te>::type,
|
|
typename std::remove_const<typename std::remove_reference<Tb>::type>::type,
|
|
typename std::remove_const<typename std::remove_reference<Te>::type>::type,
|
|
std::integral_constant<Tstep, step>
|
|
>;
|
|
|
|
template <typename Te>
|
|
xrange(const Te &) -> xrange<Te, std::integral_constant<typename std::common_type<Te, unsigned>::type, 0u>>;
|
|
|
|
template <typename Te>
|
|
xrange(Te &) -> xrange<
|
|
Te,
|
|
std::integral_constant<typename std::common_type<typename std::remove_const<Te>::type, unsigned>::type, 0u>,
|
|
Te &
|
|
>;
|
|
|
|
template <typename Te, Te e>
|
|
xrange(std::integral_constant<Te, e>) -> xrange<Te, std::integral_constant<Te, Te(0)>, std::integral_constant<Te, e>>;
|