forked from OctaForge/libostd
2070 lines
65 KiB
C++
2070 lines
65 KiB
C++
/** @defgroup Ranges
|
|
*
|
|
* @brief Ranges are the backbone of libostd iterable objects and algorithms.
|
|
*
|
|
* Ranges are simple objects that provide access to a sequence of data. They
|
|
* have a similar role to standard C++ iterators, but represent an entire
|
|
* bounded sequence, while iterators represent positions (you need two
|
|
* iterators to represent what a range is).
|
|
*
|
|
* There are input-type and output-type ranges, the former being for reading
|
|
* from and the latter for writing into. There are also hybrid ranges called
|
|
* mutable ranges, which are input-type ranges that at the same time have an
|
|
* output range interface. Input-type ranges are further divided into several
|
|
* categories, each of which extends the former. These include the basic input
|
|
* range (access to front element), forward (access to front element plus
|
|
* state copy guarantee), bidirectional (access to front and back), infinite
|
|
* random access (access to random elements), finite random access (access
|
|
* to random elements plus size and slicing) and contiguous (access to random
|
|
* elements, size, slicing, contiguous memory guarantee).
|
|
*
|
|
* There is a whole article dedicated to ranges [here](@ref ranges). You can
|
|
* also take a look at the many examples in the project tree.
|
|
*
|
|
* Some more examples:
|
|
*
|
|
* @include range.cc
|
|
*
|
|
* Pipe syntax examples:
|
|
*
|
|
* @include range_pipe.cc
|
|
*
|
|
* @{
|
|
*/
|
|
|
|
/** @file range.hh
|
|
*
|
|
* @brief The range system implementation.
|
|
*
|
|
* This file provides the actual range system implementation,
|
|
* some basic range types, iteration utilities and range traits.
|
|
*
|
|
* Some examples:
|
|
*
|
|
* @include range.cc
|
|
*
|
|
* Pipe syntax examples:
|
|
*
|
|
* @include range_pipe.cc
|
|
*
|
|
* @copyright See COPYING.md in the project tree for further information.
|
|
*/
|
|
|
|
#ifndef OSTD_RANGE_HH
|
|
#define OSTD_RANGE_HH
|
|
|
|
#include <ostd/unit_test.hh>
|
|
|
|
#include <cstddef>
|
|
#include <new>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <iterator>
|
|
#include <type_traits>
|
|
#include <initializer_list>
|
|
#include <algorithm>
|
|
#include <optional>
|
|
|
|
#define OSTD_TEST_MODULE libostd_range
|
|
|
|
namespace ostd {
|
|
|
|
/** @addtogroup Ranges
|
|
* @{
|
|
*/
|
|
|
|
/** @brief The input range tag.
|
|
*
|
|
* Every range type is identified by its tag, which essentially defines
|
|
* the capabilities of the range. Input range is the very basic one, it
|
|
* only has front element access and it doesn't guarantee that copies of
|
|
* the range won't alter other copies' state. On the other hand, as it
|
|
* provides almost no guarantees, it can be used for basically any
|
|
* object, for example I/O streams, where the current state is defined
|
|
* by the stream itself and therefore all ranges to it point to the same
|
|
* current state.
|
|
*
|
|
* You can learn more about the characteristics [here](@ref ranges).
|
|
*
|
|
* @see ostd::output_range_tag, ostd::forward_range_tag
|
|
*/
|
|
struct input_range_tag {};
|
|
|
|
/** @brief The output range tag.
|
|
*
|
|
* This tag is used for ranges that can be written into and nothing else.
|
|
* Such ranges only have the ability to put an item inside of them, with
|
|
* no further specified semantics. When an input-type range implements
|
|
* output functionality, it doesn't use this tag, see ostd::input_range_tag.
|
|
*
|
|
* You can learn more about the characteristics [here](@ref ranges).
|
|
*
|
|
* @see ostd::input_range_tag
|
|
*/
|
|
struct output_range_tag {};
|
|
|
|
/** @brief The forward range tag.
|
|
*
|
|
* This one is just like ostd::input_range_tag, it doesn't even add anything
|
|
* to the interface, but it has an extra guarantee - you can copy forward
|
|
* ranges and the copies won't affect each other's state. For example, with
|
|
* I/O streams the ranges point to a stream with some shared state, so the
|
|
* current position/element is defined by the stream itself, but with most
|
|
* other objects you can represent the current position/element within the
|
|
* range itself; for example a singly-linked list range can be forward,
|
|
* because you can have one range pointing to one node, then copy it,
|
|
* pop out the front element in the new copy and still have the previous
|
|
* range point to the old element.
|
|
*
|
|
* Any forward range is at the same time input. That's why this tag derives
|
|
* from it.
|
|
*
|
|
* You can learn more about the characteristics [here](@ref ranges).
|
|
*
|
|
* @see ostd::input_range_tag, ostd::bidirectional_range_tag
|
|
*/
|
|
struct forward_range_tag: input_range_tag {};
|
|
|
|
/** @brief The bidirectional range tag.
|
|
*
|
|
* Bidirectional ranges are forward ranges plus access to the element on
|
|
* the other side of the range. For example doubly linked lists would allow
|
|
* this kind of access. You can't directly access elements in the middle or
|
|
* count how many there are without linear complexity.
|
|
*
|
|
* You can learn more about the characteristics [here](@ref ranges).
|
|
*
|
|
* @see ostd::forward_range_tag, ostd::random_access_range_tag
|
|
*/
|
|
struct bidirectional_range_tag: forward_range_tag {};
|
|
|
|
/** @brief The infinite random access range tag.
|
|
*
|
|
* Infinite random access ranges are bidirectional ranges plus access to
|
|
* elements in the middle via an arbitrary index. They don't allow access
|
|
* to range size or slicing though, they represent a potentially infinite
|
|
* sequence.
|
|
*
|
|
* You can learn more about the characteristics [here](@ref ranges).
|
|
*
|
|
* @see ostd::bidirectional_range_tag, ostd::finite_random_access_range_tag
|
|
*/
|
|
struct random_access_range_tag: bidirectional_range_tag {};
|
|
|
|
/** @brief The finite random access range tag.
|
|
*
|
|
* Finite random access are like infinite, but they're bounded, so you can
|
|
* retrieve their size as well as make slices (think making a substring from
|
|
* a string). They do not guarantee their memory is contiguous.
|
|
*
|
|
* You can learn more about the characteristics [here](@ref ranges).
|
|
*
|
|
* @see ostd::contiguous_range_tag, ostd::random_access_range_tag
|
|
*/
|
|
struct finite_random_access_range_tag: random_access_range_tag {};
|
|
|
|
/** @brief The contiguous range tag.
|
|
*
|
|
* Contiguous ranges are the most featureful range category. They provide
|
|
* the capabilities of all previous types and they also guarantee that the
|
|
* backing memory is contiguous. A pair of pointers is a contiguous range.
|
|
*
|
|
* You can learn more about the characteristics [here](@ref ranges).
|
|
*
|
|
* @see ostd::finite_random_access_range_tag
|
|
*/
|
|
struct contiguous_range_tag: finite_random_access_range_tag {};
|
|
|
|
namespace detail {
|
|
template<typename R>
|
|
inline auto range_category_test(int) -> std::integral_constant<
|
|
bool, !std::is_void_v<typename R::range_category>
|
|
>;
|
|
|
|
template<typename>
|
|
inline std::false_type range_category_test(...);
|
|
|
|
template<typename R>
|
|
static inline constexpr bool const test_range_category =
|
|
decltype(range_category_test<R>(0))::value;
|
|
|
|
template<typename R, bool, bool>
|
|
struct range_traits_base {
|
|
template<typename>
|
|
static constexpr bool is_element_swappable_with = false;
|
|
static constexpr bool is_element_swappable = false;
|
|
template<typename>
|
|
static constexpr bool is_element_nothrow_swappable_with = false;
|
|
static constexpr bool is_element_nothrow_swappable = false;
|
|
};
|
|
|
|
template<typename R>
|
|
static inline constexpr bool const test_elem_reference =
|
|
std::is_convertible_v<typename R::range_category, input_range_tag> &&
|
|
std::is_lvalue_reference_v<typename R::reference> &&
|
|
std::is_same_v<
|
|
typename R::value_type,
|
|
std::remove_reference_t<typename R::reference>
|
|
>;
|
|
|
|
template<typename R, bool B>
|
|
struct range_traits_base<R, true, B> {
|
|
using range_category = typename R::range_category;
|
|
using size_type = typename R::size_type;
|
|
using value_type = typename R::value_type;
|
|
using reference = typename R::reference;
|
|
|
|
template<typename R2>
|
|
static constexpr bool is_element_swappable_with =
|
|
test_elem_reference<R> && test_elem_reference<R2> &&
|
|
std::is_swappable_with_v<reference, typename R2::reference>;
|
|
|
|
static constexpr bool is_element_swappable =
|
|
is_element_swappable_with<R>;
|
|
|
|
template<typename R2>
|
|
static constexpr bool is_element_nothrow_swappable_with =
|
|
test_elem_reference<R> && test_elem_reference<R2> &&
|
|
std::is_nothrow_swappable_with_v<reference, typename R2::reference>;
|
|
|
|
static constexpr bool is_element_nothrow_swappable =
|
|
is_element_nothrow_swappable_with<R>;
|
|
};
|
|
|
|
template<typename R>
|
|
struct range_traits_base<R, false, true> {
|
|
using range_category = typename R::range_category;
|
|
using value_type = typename R::value_type;
|
|
|
|
template<typename>
|
|
static constexpr bool is_element_swappable_with = false;
|
|
static constexpr bool is_element_swappable = false;
|
|
template<typename R2>
|
|
static constexpr bool is_element_nothrow_swappable_with = false;
|
|
static constexpr bool is_element_nothrow_swappable = false;
|
|
};
|
|
|
|
template<typename R, bool>
|
|
struct range_traits_impl {
|
|
template<typename>
|
|
static constexpr bool is_element_swappable_with = false;
|
|
static constexpr bool is_element_swappable = false;
|
|
template<typename>
|
|
static constexpr bool is_element_nothrow_swappable_with = false;
|
|
static constexpr bool is_element_nothrow_swappable = false;
|
|
};
|
|
|
|
template<typename R>
|
|
struct range_traits_impl<R, true>: range_traits_base<
|
|
R,
|
|
std::is_convertible_v<typename R::range_category, input_range_tag>,
|
|
std::is_convertible_v<typename R::range_category, output_range_tag>
|
|
> {};
|
|
}
|
|
|
|
/** @brief The traits (properties) of a range type.
|
|
*
|
|
* Using range traits, you can for check various properties of the range.
|
|
* If the provided `R` type is not a range type, the traits struct will
|
|
* be empty. Otherwise, it will contain the following member types:
|
|
*
|
|
* * `range_category` - one of the category tags (see ostd::input_range_tag)
|
|
* * `size_type` - can contain the range's length (typically `size_t`)
|
|
* * `value_type` - the type of the range's elements
|
|
* * `reference` - the type returned from value accessors
|
|
*
|
|
* The `size_type` and `reference` types will only be available for
|
|
* input-type ranges. Purely output ranges will only have the category
|
|
* and value type.
|
|
*
|
|
* It will always contain the following members as well:
|
|
*
|
|
* ~~~{.cc}
|
|
* template<typename R2>
|
|
* static constexpr bool is_element_swappable_with = ...;
|
|
* static constexpr bool is_element_swappable = is_element_swappable_with<R, R2>;
|
|
* template<typename R2>
|
|
* static constexpr bool is_element_nothrow_swappable_with = ...;
|
|
* static constexpr bool is_element_nothrow_swappable = is_element_swappable_with<R, R2>;
|
|
* ~~~
|
|
*
|
|
* These members will be false for non-range types as well as purely output
|
|
* range types. Otherwise, the first member tests for the following:
|
|
*
|
|
* * Both ranges are an ostd::input_range_tag or better (not an output range).
|
|
* * The `reference` member type of both ranges is an lvalue reference.
|
|
* * The `value_type` is the same as `std::remove_reference_t<reference>`.
|
|
* * The `reference` types of both ranges are swappable with each other.
|
|
*
|
|
* The references are swappable when the following is true:
|
|
*
|
|
* ~~~{.cc}
|
|
* std::is_swappable_with_v<reference, ostd::range_reference_t<R2>>
|
|
* ~~~
|
|
*
|
|
* The second member simply tests if the range's own references are swappable
|
|
* with each other. without there being another range to swap with.
|
|
*
|
|
* The nothrow versions are equivalent, except they use the nothrow versions
|
|
* of standard swappable test traits (std::is_nothrow_swappable_with_v).
|
|
*
|
|
* If any of these conditions fail, the members will be false.
|
|
*
|
|
* You can read about more details [here](@ref ranges).
|
|
*
|
|
* @see ostd::range_category_t, ostd::range_size_t, ostd::range_value_t,
|
|
* ostd::range_reference_t
|
|
*/
|
|
template<typename R>
|
|
struct range_traits: detail::range_traits_impl<R, detail::test_range_category<R>> {};
|
|
|
|
/** @brief The category tag of a range type.
|
|
*
|
|
* It's the same as doing
|
|
*
|
|
* ~~~{.cc}
|
|
* typename ostd::range_traits<R>::range_category
|
|
* ~~~
|
|
*
|
|
* Works for all types of ranges.
|
|
*
|
|
* @see ostd::range_size_t, ostd::range_value_t, ostd::range_reference_t
|
|
*/
|
|
template<typename R>
|
|
using range_category_t = typename range_traits<R>::range_category;
|
|
|
|
/** @brief The size type of a range type.
|
|
*
|
|
* It's the same as doing
|
|
*
|
|
* ~~~{.cc}
|
|
* typename ostd::range_traits<R>::size_type
|
|
* ~~~
|
|
*
|
|
* Works only for input-type ranges.
|
|
*
|
|
* @see ostd::range_category_t, ostd::range_value_t, ostd::range_reference_t
|
|
*/
|
|
template<typename R>
|
|
using range_size_t = typename range_traits<R>::size_type;
|
|
|
|
/** @brief The value type of range elements.
|
|
*
|
|
* It's the same as doing
|
|
*
|
|
* ~~~{.cc}
|
|
* typename ostd::range_traits<R>::value_type
|
|
* ~~~
|
|
*
|
|
* Works for all types of ranges.
|
|
*
|
|
* @see ostd::range_category_t, ostd::range_size_t, ostd::range_reference_t
|
|
*/
|
|
template<typename R>
|
|
using range_value_t = typename range_traits<R>::value_type;
|
|
|
|
/** @brief The type returned by range element accessors.
|
|
*
|
|
* It's the same as doing
|
|
*
|
|
* ~~~{.cc}
|
|
* typename ostd::range_traits<R>::reference
|
|
* ~~~
|
|
*
|
|
* Works only for input-type ranges.
|
|
*
|
|
* @see ostd::range_category_t, ostd::range_size_t, ostd::range_value_t
|
|
*/
|
|
template<typename R>
|
|
using range_reference_t = typename range_traits<R>::reference;
|
|
|
|
/** @brief Checks whether two ranges can swap elements with each other.
|
|
*
|
|
* It's the same as doing
|
|
*
|
|
* ~~~{.cc}
|
|
* ostd::range_traits<R1>::template is_element_swappable_with<R2>
|
|
* ~~~
|
|
*
|
|
*/
|
|
template<typename R1, typename R2>
|
|
static inline constexpr bool const is_range_element_swappable_with =
|
|
range_traits<R1>::template is_element_swappable_with<R2>;
|
|
|
|
/** @brief Checks whether a range can swap elements within itself.
|
|
*
|
|
* It's the same as doing
|
|
*
|
|
* ~~~{.cc}
|
|
* ostd::range_traits<R>::is_element_swappable
|
|
* ~~~
|
|
*
|
|
*/
|
|
template<typename R>
|
|
static inline constexpr bool const is_range_element_swappable =
|
|
range_traits<R>::is_element_swappable;
|
|
|
|
/** @brief Checks whether two ranges can nothrow swap elements with each other.
|
|
*
|
|
* It's the same as doing
|
|
*
|
|
* ~~~{.cc}
|
|
* ostd::range_traits<R1>::template is_element_nothrow_swappable_with<R2>
|
|
* ~~~
|
|
*
|
|
*/
|
|
template<typename R1, typename R2>
|
|
static inline constexpr bool const is_range_element_nothrow_swappable_with =
|
|
range_traits<R1>::template is_element_nothrow_swappable_with<R2>;
|
|
|
|
/** @brief Checks whether a range can nothrow swap elements within itself.
|
|
*
|
|
* It's the same as doing
|
|
*
|
|
* ~~~{.cc}
|
|
* ostd::range_traits<R>::is_element_nothrow_swappable
|
|
* ~~~
|
|
*
|
|
*/
|
|
template<typename R>
|
|
static inline constexpr bool const is_range_element_nothrow_swappable =
|
|
range_traits<R>::is_element_nothrow_swappable;
|
|
|
|
// is input range
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
static inline constexpr bool const is_input_range_core =
|
|
std::is_convertible_v<range_category_t<T>, input_range_tag>;
|
|
|
|
template<typename T, bool = detail::test_range_category<T>>
|
|
static inline constexpr bool const is_input_range_base = false;
|
|
|
|
template<typename T>
|
|
static inline constexpr bool const is_input_range_base<T, true> =
|
|
detail::is_input_range_core<T>;
|
|
}
|
|
|
|
/** @brief Checks if the given type is an input range or better.
|
|
*
|
|
* This check never fails, so it will return `false` for non-range types.
|
|
* Range types must be input or better for this to return `true`.
|
|
*
|
|
* @see ostd::is_forward_range, ostd::is_output_range
|
|
*/
|
|
template<typename T>
|
|
static inline constexpr bool const is_input_range =
|
|
detail::is_input_range_base<T>;
|
|
|
|
// is forward range
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
static inline constexpr bool const is_forward_range_core =
|
|
std::is_convertible_v<range_category_t<T>, forward_range_tag>;
|
|
|
|
template<typename T, bool = detail::test_range_category<T>>
|
|
static inline constexpr bool const is_forward_range_base = false;
|
|
|
|
template<typename T>
|
|
static inline constexpr bool const is_forward_range_base<T, true> =
|
|
detail::is_forward_range_core<T>;
|
|
}
|
|
|
|
/** @brief Checks if the given type is a forward range or better.
|
|
*
|
|
* This check never fails, so it will return `false` for non-range types.
|
|
* Range types must be forward or better for this to return `true`.
|
|
*
|
|
* @see ostd::is_input_range, ostd::is_bidirectional_range
|
|
*/
|
|
template<typename T>
|
|
static inline constexpr bool const is_forward_range =
|
|
detail::is_forward_range_base<T>;
|
|
|
|
// is bidirectional range
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
static inline constexpr bool const is_bidirectional_range_core =
|
|
std::is_convertible_v<range_category_t<T>, bidirectional_range_tag>;
|
|
|
|
template<typename T, bool = detail::test_range_category<T>>
|
|
static inline constexpr bool const is_bidirectional_range_base = false;
|
|
|
|
template<typename T>
|
|
static inline constexpr bool const is_bidirectional_range_base<T, true> =
|
|
detail::is_bidirectional_range_core<T>;
|
|
}
|
|
|
|
/** @brief Checks if the given type is a bidirectional range or better.
|
|
*
|
|
* This check never fails, so it will return `false` for non-range types.
|
|
* Range types must be bidirectional or better for this to return `true`.
|
|
*
|
|
* @see ostd::is_forward_range, ostd::is_random_access_range
|
|
*/
|
|
template<typename T>
|
|
static inline constexpr bool const is_bidirectional_range =
|
|
detail::is_bidirectional_range_base<T>;
|
|
|
|
// is random access range
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
static inline constexpr bool const is_random_access_range_core =
|
|
std::is_convertible_v<range_category_t<T>, random_access_range_tag>;
|
|
|
|
template<typename T, bool = detail::test_range_category<T>>
|
|
static inline constexpr bool const is_random_access_range_base = false;
|
|
|
|
template<typename T>
|
|
static inline constexpr bool const is_random_access_range_base<T, true> =
|
|
detail::is_random_access_range_core<T>;
|
|
}
|
|
|
|
/** @brief Checks if the given type is a random access range or better.
|
|
*
|
|
* This check never fails, so it will return `false` for non-range types.
|
|
* Range types must be infinite random access or better for this to
|
|
* return `true`.
|
|
*
|
|
* @see ostd::is_bidirectional_range, ostd::is_finite_random_access_range
|
|
*/
|
|
template<typename T>
|
|
static inline constexpr bool const is_random_access_range =
|
|
detail::is_random_access_range_base<T>;
|
|
|
|
// is finite random access range
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
static inline constexpr bool const is_finite_random_access_range_core =
|
|
std::is_convertible_v<range_category_t<T>, finite_random_access_range_tag>;
|
|
|
|
template<typename T, bool = detail::test_range_category<T>>
|
|
static inline constexpr bool const is_finite_random_access_range_base = false;
|
|
|
|
template<typename T>
|
|
static inline constexpr bool const is_finite_random_access_range_base<T, true> =
|
|
detail::is_finite_random_access_range_core<T>;
|
|
}
|
|
|
|
/** @brief Checks if the given type is a finite random access range or better.
|
|
*
|
|
* This check never fails, so it will return `false` for non-range types.
|
|
* Range types must be finite random access or better for this to
|
|
* return `true`.
|
|
*
|
|
* @see ostd::is_random_access_range, ostd::is_contiguous_range
|
|
*/
|
|
template<typename T>
|
|
static inline constexpr bool const is_finite_random_access_range =
|
|
detail::is_finite_random_access_range_base<T>;
|
|
|
|
// is contiguous range
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
static inline constexpr bool const is_contiguous_range_core =
|
|
std::is_convertible_v<range_category_t<T>, contiguous_range_tag>;
|
|
|
|
template<typename T, bool = detail::test_range_category<T>>
|
|
static inline constexpr bool const is_contiguous_range_base = false;
|
|
|
|
template<typename T>
|
|
static inline constexpr bool const is_contiguous_range_base<T, true> =
|
|
detail::is_contiguous_range_core<T>;
|
|
}
|
|
|
|
/** @brief Checks if the given type is a contiguous range.
|
|
*
|
|
* This check never fails, so it will return `false` for non-range types.
|
|
*
|
|
* @see ostd::is_finite_random_access_range
|
|
*/
|
|
template<typename T>
|
|
static inline constexpr bool const is_contiguous_range =
|
|
detail::is_contiguous_range_base<T>;
|
|
|
|
// is output range
|
|
|
|
namespace detail {
|
|
template<typename R, typename T>
|
|
inline auto test_outrange(int) -> typename std::is_void<
|
|
decltype(std::declval<R &>().put(std::declval<T>()))
|
|
>::tyoe;
|
|
|
|
template<typename, typename>
|
|
inline std::false_type test_outrange(...);
|
|
|
|
template<typename R, typename T>
|
|
static inline constexpr bool const output_range_test =
|
|
decltype(test_outrange<R, T>())::value;
|
|
|
|
template<typename T>
|
|
static inline constexpr bool const is_output_range_core =
|
|
std::is_convertible_v<range_category_t<T>, output_range_tag> || (
|
|
is_input_range<T> && (
|
|
output_range_test<T, range_value_t<T> const &> ||
|
|
output_range_test<T, range_value_t<T> &&> ||
|
|
output_range_test<T, range_value_t<T> >
|
|
)
|
|
);
|
|
|
|
template<typename T, bool = detail::test_range_category<T>>
|
|
static inline constexpr bool const is_output_range_base = false;
|
|
|
|
template<typename T>
|
|
static inline constexpr bool const is_output_range_base<T, true> =
|
|
detail::is_output_range_core<T>;
|
|
}
|
|
|
|
/** @brief Checks if the given type is an output range.
|
|
*
|
|
* This check never fails, so it will return `false` for non-range types.
|
|
* Range types must either have ostd::output_range_tag or implement the
|
|
* output range capabilities, this doesn't merely check the tag but it
|
|
* also checks the interface if the tag is not present.
|
|
*
|
|
* @see ostd::is_input_range
|
|
*/
|
|
template<typename T>
|
|
static inline constexpr bool const is_output_range =
|
|
detail::is_output_range_base<T>;
|
|
|
|
namespace detail {
|
|
// range iterator
|
|
template<typename T>
|
|
struct range_iterator {
|
|
range_iterator() = delete;
|
|
explicit range_iterator(T const &range): p_range(range) {}
|
|
explicit range_iterator(T &&range): p_range(std::move(range)) {}
|
|
range_iterator &operator++() {
|
|
p_range.pop_front();
|
|
return *this;
|
|
}
|
|
range_reference_t<T> operator*() const {
|
|
return p_range.front();
|
|
}
|
|
bool operator!=(std::nullptr_t) const {
|
|
return !p_range.empty();
|
|
}
|
|
private:
|
|
T p_range;
|
|
};
|
|
|
|
// complete range iterator
|
|
template<typename T>
|
|
struct full_range_iterator {
|
|
using iterator_category = std::input_iterator_tag;
|
|
using value_type = range_value_t<T>;
|
|
using reference = range_reference_t<T>;
|
|
using pointer = value_type *;
|
|
using difference_type = std::make_signed_t<range_size_t<T>>;
|
|
|
|
full_range_iterator(): p_range(std::nullopt) {}
|
|
full_range_iterator(T const &range): p_range(range) {}
|
|
|
|
bool operator==(full_range_iterator const &v) const {
|
|
if (!p_range) {
|
|
if (v.p_range) {
|
|
return (*v.p_range).empty();
|
|
}
|
|
return true;
|
|
}
|
|
return (*p_range).empty();
|
|
}
|
|
bool operator!=(full_range_iterator const &v) const {
|
|
return !((*this) == v);
|
|
}
|
|
|
|
reference operator*() const {
|
|
return (*p_range).front();
|
|
}
|
|
|
|
full_range_iterator &operator++() {
|
|
p_range.value().pop_front();
|
|
return *this;
|
|
}
|
|
full_range_iterator operator++(int) {
|
|
full_range_iterator ret{p_range.value()};
|
|
(*p_range).pop_front();
|
|
return ret;
|
|
}
|
|
private:
|
|
std::optional<T> p_range;
|
|
};
|
|
}
|
|
|
|
namespace detail {
|
|
template<typename>
|
|
struct reverse_range;
|
|
template<typename>
|
|
struct move_range;
|
|
template<typename>
|
|
struct enumerated_range;
|
|
template<typename>
|
|
struct take_range;
|
|
template<typename>
|
|
struct chunks_range;
|
|
template<typename ...>
|
|
struct join_range;
|
|
template<typename ...>
|
|
struct zip_range;
|
|
}
|
|
|
|
/** @brief Pops out at most `n` elements from the front of the given range.
|
|
*
|
|
* For finite random access ranges (ostd::finite_random_access_range_tag)
|
|
* or better, slicing is used. Otherwise, each element is popped out
|
|
* separately. Therefore, the complexity either depends on the slciing
|
|
* operation (frequently `O(1)`) or is linear (`O(n)`).
|
|
*
|
|
* If the range doesn't have the given number of elements, it will pop
|
|
* out at least what's there.
|
|
*
|
|
* The range type must be an input range (ostd::input_range_tag) or better.
|
|
*
|
|
* @returns The number of elements popped out.
|
|
*
|
|
* @see ostd::range_pop_back_n()
|
|
*/
|
|
template<typename IR>
|
|
inline range_size_t<IR> range_pop_front_n(IR &range, range_size_t<IR> n) {
|
|
if constexpr(std::is_convertible_v<
|
|
range_category_t<IR>, finite_random_access_range_tag
|
|
>) {
|
|
auto rs = range.size();
|
|
n = std::min(n, rs);
|
|
range = range.slice(n, rs);
|
|
return n;
|
|
} else {
|
|
range_size_t<IR> r = 0;
|
|
while (n-- && !range.empty()) {
|
|
range.pop_front();
|
|
++r;
|
|
}
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/** @brief Pops out at most `n` elements from the back of the given range.
|
|
*
|
|
* For finite random access ranges (ostd::finite_random_access_range_tag)
|
|
* or better, slicing is used. Otherwise, each element is popped out
|
|
* separately. Therefore, the complexity either depends on the slciing
|
|
* operation (frequently `O(1)`) or is linear (`O(n)`).
|
|
*
|
|
* If the range doesn't have the given number of elements, it will pop
|
|
* out at least what's there.
|
|
*
|
|
* The range type must be a bidirectionalrange (ostd::bidirectional_range_tag)
|
|
* or better.
|
|
*
|
|
* @returns The number of elements popped out.
|
|
*
|
|
* @see ostd::range_pop_front_n()
|
|
*/
|
|
template<typename IR>
|
|
inline range_size_t<IR> range_pop_back_n(IR &range, range_size_t<IR> n) {
|
|
if constexpr(std::is_convertible_v<
|
|
range_category_t<IR>, finite_random_access_range_tag
|
|
>) {
|
|
auto rs = range.size();
|
|
n = std::min(n, rs);
|
|
range = range.slice(0, rs - n);
|
|
return n;
|
|
} else {
|
|
range_size_t<IR> r = 0;
|
|
while (n-- && !range.empty()) {
|
|
range.pop_back();
|
|
++r;
|
|
}
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/** @brief A base type for all input-type ranges to derive from.
|
|
*
|
|
* Every input range type derives from this, see [Ranges](@ref ranges).
|
|
* It implements functionality every range type is supposed to have.
|
|
*
|
|
* Pure output ranges will need to derive from ostd::output_range.
|
|
*
|
|
* The pipe operator overloads call the given function with this range.
|
|
* This allows pipeable algorithms which essentially return a lambda taking
|
|
* the range which then performs the needed algorithm.
|
|
*/
|
|
template<typename B>
|
|
struct input_range {
|
|
/** @brief A complete input iterator type for the range.
|
|
*
|
|
* This is a complete iterator type that you can create with
|
|
* iter_begin(). This iterator can only be used to represent
|
|
* the full range sequence - i.e. from the beginning to the
|
|
* end of the range, with the end always being iter_end().
|
|
*
|
|
* Using an incremented version of iter_begin() as an ending is
|
|
* undefined as the ending iterator is not used by itself, this
|
|
* is why it's an input iterator type (if the function this is
|
|
* used with tried to use a temporary copy of the iterator as
|
|
* an end, it'd result in incorrect behavior). Thus, this is
|
|
* mostly meant to insert ranges into data structures that
|
|
* take input iterators in their insertion methods.
|
|
*/
|
|
using full_iterator = detail::full_range_iterator<B>;
|
|
|
|
/** @brief Creates a very simple iterator for range-based for loop.
|
|
*
|
|
* Thanks to this, you can use the range-based for loop with ranges
|
|
* effortlessly. It's not a proper full iterator type, it only has
|
|
* the necessary functionality for iteration and has no end iterator,
|
|
* see end().
|
|
*/
|
|
auto begin() const {
|
|
return detail::range_iterator<B>(*static_cast<B const *>(this));
|
|
}
|
|
|
|
/** @brief Gets a sentinel for begin(). */
|
|
std::nullptr_t end() const {
|
|
return nullptr;
|
|
}
|
|
|
|
/** @brief Constructs a full_iterator for the range. */
|
|
full_iterator iter_begin() const {
|
|
return full_iterator{*static_cast<B const *>(this)};
|
|
}
|
|
|
|
/** @brief Constructs a full_iterator end. */
|
|
full_iterator iter_end() const {
|
|
return full_iterator{};
|
|
}
|
|
|
|
/** @brief Creates a copy of the range.
|
|
*
|
|
* This is just like copy-constructing the range.
|
|
*/
|
|
B iter() const {
|
|
return B(*static_cast<B const *>(this));
|
|
}
|
|
|
|
/** @brief Gets a reverse range to the current range.
|
|
*
|
|
* A reverse range is a wrapper range that lazily reveses the direction.
|
|
* Only defined for ranges that are at least bidirectional. It re-routes
|
|
* the access so that front accesses back and vice versa.
|
|
*
|
|
* If this range is at least ostd::finite_ranodm_access_range_tag, the
|
|
* wrapped range will be always ostd::finite_random_access_range_tag.
|
|
* Otherwise, it will be ostd::bidirectional_range_tag.
|
|
*
|
|
* The value, reference and size types are the same.
|
|
*/
|
|
auto reverse() const {
|
|
static_assert(
|
|
is_bidirectional_range<B>,
|
|
"Wrapped range must be bidirectional or better"
|
|
);
|
|
return detail::reverse_range<B>(iter());
|
|
}
|
|
|
|
/** @brief Gets a wrapper range that moves all the elements.
|
|
*
|
|
* The range is an ostd::input_range_tag. The base range is required to
|
|
* have real references for its reference type and those references must
|
|
* be references to the range's value type, i.e.
|
|
*
|
|
* ~~~{.cc}
|
|
* std::is_reference_v<ostd::range_reference_t<R>> == true
|
|
* std::is_same_v<
|
|
* ostd::range_value_t<T>,
|
|
* std::remove_reference_t<ostd::range_reference_t<T>
|
|
* > == true
|
|
* ~~~
|
|
*
|
|
* The value and size types remain the same. The new reference
|
|
* type becomes `ostd::range_value_t<R> &&`.
|
|
*
|
|
* Accesses to the front member result in the element being moved.
|
|
*/
|
|
auto movable() const {
|
|
return detail::move_range<B>(iter());
|
|
}
|
|
|
|
/** @brief Gets an enumerated range for the range.
|
|
*
|
|
* An enumerated range wraps the original range in a way where the accesses
|
|
* remain mostly the same, but an index counter is kept and incremented on
|
|
* each pop.
|
|
*
|
|
* It's always at most ostd::forward_range_tag. The value and size types
|
|
* stay the same, the new reference type is like this:
|
|
*
|
|
* ~~~{.cc}
|
|
* struct enumerated_value_t {
|
|
* range_size_t<R> index;
|
|
* range_reference_t<R> value;
|
|
* };
|
|
* ~~~
|
|
*
|
|
* This is useful when you need to iterate over a range using the range
|
|
* based for loop but still need a numerical index. An example:
|
|
*
|
|
* ~~~{.cc}
|
|
* auto il = { 5, 10, 15};
|
|
* // prints "0: 5", "1: 10", "2: 15"
|
|
* for (auto v: ostd::iter(il).enumerate()) {
|
|
* ostd::writefln("%s: %s", v.index, v.value);
|
|
* }
|
|
* ~~~
|
|
*/
|
|
auto enumerate() const {
|
|
return detail::enumerated_range<B>(iter());
|
|
}
|
|
|
|
/** @brief Gets a range representing several elements of the range.
|
|
*
|
|
* Wraps the range in a way where at most `n` elements are considered
|
|
* when manipulating the range. The result is at always at most
|
|
* ostd::forward_range_tag.
|
|
*
|
|
* It is undefined behavior to try to `pop_front()` past the internal
|
|
* counter (i.e. `empty()` must not be true when calling it).
|
|
*/
|
|
auto take(std::size_t n) const {
|
|
return detail::take_range<B>(iter(), n);
|
|
}
|
|
|
|
/** @brief Splits the range into range of chunks.
|
|
*
|
|
* The resulting range is at most ostd::forward_range_tag. Each element
|
|
* of it is the result of take() on the current stored range. Each call
|
|
* to `pop_front()` pops out at most `n` elements from the wrapped range.
|
|
*
|
|
* If the wrapped range's length is not a multiple of `n`, the last chunk
|
|
* will have fewer elements than `n`.
|
|
*/
|
|
auto chunks(std::size_t n) const {
|
|
return detail::chunks_range<B>(iter(), n);
|
|
}
|
|
|
|
/** @brief Joins multiple ranges together.
|
|
*
|
|
* The ranges don't have to be the same. The types of ostd::range_traits
|
|
* will be std::common_type_t of all ranges' trait types. The range itself
|
|
* is at most ostd::forward_range_tag, but can be ostd::input_range_tag
|
|
* if any of the joined ranges are.
|
|
*
|
|
* The range is empty when all joined ranges are empty. Access to front
|
|
* is undefined if all joined ranges are empty.
|
|
*/
|
|
template<typename R1, typename ...RR>
|
|
auto join(R1 r1, RR ...rr) const {
|
|
return detail::join_range<B, R1, RR...>(
|
|
iter(), std::move(r1), std::move(rr)...
|
|
);
|
|
}
|
|
|
|
/** @brief Zips multiple ranges together.
|
|
*
|
|
* The ranges will all be iterated at the same time up until the shortest
|
|
* range's length. The wrapper range is at most ostd::forward_range_tag.
|
|
*
|
|
* The value type can be a pair (for two ranges) or a tuple (for more) of
|
|
* the value types. The reference type is also a pair or a tuple, but of
|
|
* the reference types. The size type is the common type between the
|
|
* zipped ranges.
|
|
*/
|
|
template<typename R1, typename ...RR>
|
|
auto zip(R1 r1, RR ...rr) const {
|
|
return detail::zip_range<B, R1, RR...>(
|
|
iter(), std::move(r1), std::move(rr)...
|
|
);
|
|
}
|
|
|
|
/** @brief Simpler syntax for accessing the front element. */
|
|
auto operator*() const {
|
|
return std::forward<decltype(static_cast<B const *>(this)->front())>(
|
|
static_cast<B const *>(this)->front()
|
|
);
|
|
}
|
|
|
|
/** @brief Simpler syntax for popping out the front element. */
|
|
B &operator++() {
|
|
static_cast<B *>(this)->pop_front();
|
|
return *static_cast<B *>(this);
|
|
}
|
|
|
|
/** @brief Simpler syntax for popping out the front element.
|
|
*
|
|
* Returns the range before the pop happened.
|
|
*/
|
|
B operator++(int) {
|
|
B tmp(*static_cast<B const *>(this));
|
|
static_cast<B *>(this)->pop_front();
|
|
return tmp;
|
|
}
|
|
|
|
/* pipe op, must be a member to work for user ranges automagically */
|
|
|
|
/** @brief A necessary overload for pipeable algorithm support. */
|
|
template<typename F>
|
|
auto operator|(F &&func) {
|
|
return func(*static_cast<B *>(this));
|
|
}
|
|
|
|
/** @brief A necessary overload for pipeable algorithm support. */
|
|
template<typename F>
|
|
auto operator|(F &&func) const {
|
|
return func(*static_cast<B const *>(this));
|
|
}
|
|
|
|
/* universal bool operator */
|
|
|
|
/** @brief Checks if the range is not empty. */
|
|
explicit operator bool() const {
|
|
return !(static_cast<B const *>(this)->empty());
|
|
}
|
|
};
|
|
|
|
/** @brief The base type for all pure output ranges. */
|
|
template<typename B>
|
|
struct output_range {
|
|
/** @brief All pure output ranges have the same tag. */
|
|
using range_category = output_range_tag;
|
|
};
|
|
|
|
/** @brief Puts all of `range`'s elements into `orange`.
|
|
*
|
|
* The default implementation is equivalent to iterating `range` and then
|
|
* calling `orange.put(range.front())` on each, but it can be overloaded
|
|
* with more efficient implementations per type. Usages of this in generic
|
|
* algortihms follow ADL, so the right function will always be resolved.
|
|
*/
|
|
template<typename OR, typename IR>
|
|
inline void range_put_all(OR &orange, IR range) {
|
|
for (; !range.empty(); range.pop_front()) {
|
|
orange.put(range.front());
|
|
}
|
|
}
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
struct noop_output_range: output_range<noop_output_range<T>> {
|
|
using value_type = T;
|
|
|
|
/** @brief Has no effect. */
|
|
void put(T const &) {}
|
|
};
|
|
}
|
|
|
|
/** @brief Creates an output range that does nothing.
|
|
*
|
|
* This is a complete output range, but it doesn't actually store the given
|
|
* values, instead it simply does nothing. Useful in metaprogramming and
|
|
* when you need to go over a portion of something that uses sinks.
|
|
*/
|
|
template<typename T>
|
|
inline auto noop_sink() {
|
|
return detail::noop_output_range<T>{};
|
|
}
|
|
|
|
namespace detail {
|
|
template<typename R>
|
|
struct counting_output_range: output_range<counting_output_range<R>> {
|
|
using value_type = range_value_t<R>;
|
|
|
|
private:
|
|
R p_range;
|
|
size_t p_written = 0;
|
|
|
|
public:
|
|
counting_output_range() = delete;
|
|
|
|
counting_output_range(R const &range): p_range(range) {}
|
|
|
|
void put(value_type const &v) {
|
|
p_range.put(v);
|
|
++p_written;
|
|
}
|
|
|
|
void put(value_type &&v) {
|
|
p_range.put(std::move(v));
|
|
++p_written;
|
|
}
|
|
|
|
size_t get_written() const {
|
|
return p_written;
|
|
}
|
|
|
|
R &get_range() {
|
|
return p_range;
|
|
}
|
|
|
|
R const &get_range() const {
|
|
return p_range;
|
|
}
|
|
};
|
|
}
|
|
|
|
/** @brief Creates an output range that counts items put into it.
|
|
*
|
|
* Takes an output range and creates a wrapper output range around it that
|
|
* counts each item put into it. A copy of the range is stored. The types
|
|
* are inherited from the wrapped range.
|
|
*
|
|
* This method is provided to retrieve the number of values put into the range:
|
|
*
|
|
* ~~~{.cc}
|
|
* range_size_t<R> get_written() const;
|
|
* ~~~
|
|
*
|
|
* These methods are provided to retrieve the range:
|
|
*
|
|
* ~~~{.cc}
|
|
* R &get_range();
|
|
* R const &get_range() const;
|
|
* ~~~
|
|
*/
|
|
template<typename R>
|
|
inline auto counting_sink(R const &range) {
|
|
return detail::counting_output_range<R>{range};
|
|
}
|
|
|
|
/** @brief A pipeable version of ostd::input_range::reverse(). */
|
|
inline auto reverse() {
|
|
return [](auto &obj) { return obj.reverse(); };
|
|
}
|
|
|
|
/** @brief A pipeable version of ostd::input_range::movable(). */
|
|
inline auto movable() {
|
|
return [](auto &obj) { return obj.movable(); };
|
|
}
|
|
|
|
/** @brief A pipeable version of ostd::input_range::enumerate(). */
|
|
inline auto enumerate() {
|
|
return [](auto &obj) { return obj.enumerate(); };
|
|
}
|
|
|
|
/** @brief A pipeable version of ostd::input_range::take(). */
|
|
inline auto take(std::size_t n) {
|
|
return [n](auto &obj) { return obj.take(n); };
|
|
}
|
|
|
|
/** @brief A pipeable version of ostd::input_range::chunks(). */
|
|
inline auto chunks(std::size_t n) {
|
|
return [n](auto &obj) { return obj.chunks(n); };
|
|
}
|
|
|
|
/** @brief A pipeable version of ostd::input_range::join(). */
|
|
template<typename R>
|
|
inline auto join(R &&range) {
|
|
return [range = std::forward<R>(range)](auto &obj) mutable {
|
|
return obj.join(std::forward<R>(range));
|
|
};
|
|
}
|
|
|
|
/** @brief A pipeable version of ostd::input_range::join(). */
|
|
template<typename R1, typename ...R>
|
|
inline auto join(R1 &&r1, R &&...rr) {
|
|
return [
|
|
ranges = std::forward_as_tuple(
|
|
std::forward<R1>(r1), std::forward<R>(rr)...
|
|
)
|
|
] (auto &obj) mutable {
|
|
return std::apply([&obj](auto &&...args) mutable {
|
|
return obj.join(std::forward<decltype(args)>(args)...);
|
|
}, std::move(ranges));
|
|
};
|
|
}
|
|
|
|
/** @brief A pipeable version of ostd::input_range::zip(). */
|
|
template<typename R>
|
|
inline auto zip(R &&range) {
|
|
return [range = std::forward<R>(range)](auto &obj) mutable {
|
|
return obj.zip(std::forward<R>(range));
|
|
};
|
|
}
|
|
|
|
/** @brief A pipeable version of ostd::input_range::zip(). */
|
|
template<typename R1, typename ...R>
|
|
inline auto zip(R1 &&r1, R &&...rr) {
|
|
return [
|
|
ranges = std::forward_as_tuple(
|
|
std::forward<R1>(r1), std::forward<R>(rr)...
|
|
)
|
|
] (auto &obj) mutable {
|
|
return std::apply([&obj](auto &&...args) mutable {
|
|
return obj.zip(std::forward<decltype(args)>(args)...);
|
|
}, std::move(ranges));
|
|
};
|
|
}
|
|
|
|
namespace detail {
|
|
template<typename C>
|
|
inline auto test_direct_iter(int) -> std::integral_constant<
|
|
bool, is_input_range<decltype(std::declval<C &>().iter())>
|
|
>;
|
|
|
|
template<typename>
|
|
inline std::false_type test_direct_iter(...);
|
|
|
|
template<typename C>
|
|
static inline constexpr bool const direct_iter_test =
|
|
decltype(test_direct_iter<C>(0))::value;
|
|
|
|
|
|
template<typename C>
|
|
inline auto test_std_iter(int) -> std::integral_constant<
|
|
bool,
|
|
!std::is_void_v<decltype(std::begin(std::declval<C &>()))> &&
|
|
!std::is_void_v<decltype(std::end(std::declval<C &>()))>
|
|
>;
|
|
|
|
template<typename>
|
|
inline std::false_type test_std_iter(...);
|
|
|
|
template<typename C>
|
|
static inline constexpr bool const std_iter_test =
|
|
decltype(test_std_iter<C>(0))::value;
|
|
|
|
/* direct iter and std iter; the case for std iter is
|
|
* specialized after iterator_range is actually defined
|
|
*/
|
|
template<typename C, bool, bool>
|
|
struct ranged_traits_core {};
|
|
|
|
/* direct iter is available, regardless of std iter being available */
|
|
template<typename C, bool B>
|
|
struct ranged_traits_core<C, true, B> {
|
|
using range = decltype(std::declval<C &>().iter());
|
|
|
|
static range iter(C &r) {
|
|
return r.iter();
|
|
}
|
|
};
|
|
|
|
template<typename C>
|
|
struct ranged_traits_impl: ranged_traits_core<
|
|
C, direct_iter_test<C>, std_iter_test<C>
|
|
> {};
|
|
}
|
|
|
|
/** @brief A structure to implement iterability of any object.
|
|
*
|
|
* Custom types can specialize this. It's also specialized in multiple
|
|
* places in libostd. It needs to implement one member alias which is
|
|
* `using range = ...` representing the range type and one method which
|
|
* is `static range iter(C &)`.
|
|
*
|
|
* This default implementation handles all objects which implement the
|
|
* `iter()` method that returns a range, as well as all objects that
|
|
* have the standard iterator interface, using ostd::iterator_range.
|
|
*
|
|
* Because of these two default generic cases, you frequently won't need to
|
|
* specialize this at all.
|
|
*
|
|
* Const and non-const versions need separate specializations.
|
|
*/
|
|
template<typename C>
|
|
struct ranged_traits: detail::ranged_traits_impl<C> {};
|
|
|
|
/** @brief A generic function to iterate any object.
|
|
*
|
|
* This uses ostd::ranged_traits to create the range object, so it will
|
|
* work for anything ostd::ranged_traits is properly specialized for.
|
|
*
|
|
* @see ostd::citer()
|
|
*/
|
|
template<typename T>
|
|
inline auto iter(T &&r) ->
|
|
decltype(ranged_traits<std::remove_reference_t<T>>::iter(r))
|
|
{
|
|
return ranged_traits<std::remove_reference_t<T>>::iter(r);
|
|
}
|
|
|
|
/** @brief A generic function to iterate an immutable version of any object.
|
|
*
|
|
* This uses ostd::ranged_traits to create the range object, so it will
|
|
* work for anything ostd::ranged_traits is properly specialized for.
|
|
* It forces a range for an immutable object, unlike ostd::iter(), where
|
|
* it depends on the constness of the input.
|
|
*
|
|
* @see ostd::iter()
|
|
*/
|
|
template<typename T>
|
|
inline auto citer(T const &r) -> decltype(ranged_traits<T const>::iter(r)) {
|
|
return ranged_traits<T const>::iter(r);
|
|
}
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
inline auto test_iterable(int) -> std::integral_constant<
|
|
bool, is_input_range<decltype(ostd::iter(std::declval<T>()))>
|
|
>;
|
|
template<typename>
|
|
inline std::false_type test_iterable(...);
|
|
|
|
template<typename T>
|
|
static inline constexpr bool const iterable_test =
|
|
decltype(test_iterable<T>(0))::value;
|
|
}
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
struct number_range: input_range<number_range<T>> {
|
|
using range_category = forward_range_tag;
|
|
using value_type = T;
|
|
using reference = T;
|
|
using size_type = std::size_t;
|
|
|
|
number_range() = delete;
|
|
|
|
number_range(T a, T b, std::make_signed_t<T> step = 1):
|
|
p_a(a), p_b(b), p_step(step)
|
|
{}
|
|
|
|
number_range(T v): p_a(0), p_b(v), p_step(1) {}
|
|
|
|
bool empty() const { return p_a * p_step >= p_b * p_step; }
|
|
|
|
void pop_front() { p_a += p_step; }
|
|
|
|
reference front() const { return p_a; }
|
|
|
|
private:
|
|
T p_a, p_b;
|
|
std::make_signed_t<T> p_step;
|
|
};
|
|
} /* namespace detail */
|
|
|
|
/** @brief Creates an integer interval between `a` and `b`.
|
|
*
|
|
* The result is an ostd::forward_range_tag range with `T` for value
|
|
* and reference types. If `T` is unsigned, `step` is a signed version of
|
|
* it. If it's signed, `step` is the same type.
|
|
*
|
|
* The range goes from `a` until `b`, incrementing by `step` with each
|
|
* pop. The `b` boudnary is not included in the range (it's half open).
|
|
* It's considered empty once `(current * step) >= (b * step)`, with
|
|
* `current` being `a` at first, increased by `step` on each pop.
|
|
*
|
|
* This allows writing nice code such as
|
|
*
|
|
* ~~~{.cc}
|
|
* for (int i: ostd::range(5, 10)) { // from 5 to 10, not including 10
|
|
* ...
|
|
* }
|
|
* ~~~
|
|
*/
|
|
template<typename T>
|
|
inline auto range(T a, T b, std::make_signed_t<T> step = 1) {
|
|
return detail::number_range<T>(a, b, step);
|
|
}
|
|
|
|
/** @brief Creates an integer interval between `0` and `v`.
|
|
*
|
|
* Equivalent to ostd::range() with 0 and `v` for arguments.
|
|
* This allows writing nice code such as
|
|
*
|
|
* ~~~{.cc}
|
|
* for (int i: ostd::range(10)) { // from 0 to 10, not including 10
|
|
* ...
|
|
* }
|
|
* ~~~
|
|
*/
|
|
template<typename T>
|
|
inline auto range(T v) {
|
|
return detail::number_range<T>(v);
|
|
}
|
|
|
|
#ifdef OSTD_BUILD_TESTS
|
|
OSTD_UNIT_TEST {
|
|
using ostd::test::fail_if;
|
|
auto r = range(5, 10, 2);
|
|
fail_if(r.empty() || (r.front() != 5));
|
|
r.pop_front();
|
|
fail_if(r.empty() || (r.front() != 7));
|
|
r.pop_front();
|
|
fail_if(r.empty() || (r.front() != 9));
|
|
r.pop_front();
|
|
fail_if(!r.empty());
|
|
}
|
|
#endif
|
|
|
|
namespace detail {
|
|
template<typename T>
|
|
struct reverse_range: input_range<reverse_range<T>> {
|
|
using range_category = std::conditional_t<
|
|
is_finite_random_access_range<T>,
|
|
finite_random_access_range_tag,
|
|
bidirectional_range_tag
|
|
>;
|
|
using value_type = range_value_t <T>;
|
|
using reference = range_reference_t<T>;
|
|
using size_type = range_size_t <T>;
|
|
|
|
private:
|
|
T p_range;
|
|
|
|
public:
|
|
reverse_range() = delete;
|
|
|
|
reverse_range(T const &v): p_range(v) {}
|
|
|
|
bool empty() const { return p_range.empty(); }
|
|
|
|
size_type size() const { return p_range.size(); }
|
|
|
|
void pop_front() { p_range.pop_back(); }
|
|
void pop_back() { p_range.pop_front(); }
|
|
|
|
reference front() const { return p_range.back(); }
|
|
reference back() const { return p_range.front(); }
|
|
|
|
reference operator[](size_type i) const {
|
|
return p_range[size() - i - 1];
|
|
}
|
|
|
|
reverse_range slice(size_type start, size_type end) const {
|
|
size_type len = p_range.size();
|
|
return reverse_range{p_range.slice(len - end, len - start)};
|
|
}
|
|
|
|
reverse_range slice(size_type start) const {
|
|
return slice(start, size());
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
struct move_range: input_range<move_range<T>> {
|
|
static_assert(
|
|
std::is_reference_v<range_reference_t<T>>,
|
|
"Wrapped range must return proper references for accessors"
|
|
);
|
|
static_assert(
|
|
std::is_same_v<
|
|
range_value_t<T>, std::remove_reference_t<range_reference_t<T>>
|
|
>,
|
|
"Wrapped range references must be proper references to the value type"
|
|
);
|
|
|
|
using range_category = input_range_tag;
|
|
using value_type = range_value_t<T>;
|
|
using reference = range_value_t<T> &&;
|
|
using size_type = range_size_t<T>;
|
|
|
|
private:
|
|
T p_range;
|
|
|
|
public:
|
|
move_range() = delete;
|
|
|
|
move_range(T const &range): p_range(range) {}
|
|
|
|
bool empty() const { return p_range.empty(); }
|
|
|
|
void pop_front() { p_range.pop_front(); }
|
|
|
|
reference front() const { return std::move(p_range.front()); }
|
|
};
|
|
|
|
template<typename T>
|
|
struct enumerated_range: input_range<enumerated_range<T>> {
|
|
private:
|
|
struct enumerated_value_t {
|
|
range_size_t<T> index;
|
|
range_reference_t<T> value;
|
|
};
|
|
|
|
public:
|
|
using range_category = std::common_type_t<
|
|
range_category_t<T>, forward_range_tag
|
|
>;
|
|
using value_type = range_value_t<T>;
|
|
using reference = enumerated_value_t;
|
|
using size_type = range_size_t<T>;
|
|
|
|
private:
|
|
T p_range;
|
|
size_type p_index;
|
|
|
|
public:
|
|
enumerated_range() = delete;
|
|
enumerated_range(T const &range): p_range(range), p_index(0) {}
|
|
|
|
bool empty() const { return p_range.empty(); }
|
|
|
|
void pop_front() {
|
|
p_range.pop_front();
|
|
++p_index;
|
|
}
|
|
|
|
reference front() const {
|
|
return reference{p_index, p_range.front()};
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
struct take_range: input_range<take_range<T>> {
|
|
using range_category = std::common_type_t<
|
|
range_category_t<T>, forward_range_tag
|
|
>;
|
|
using value_type = range_value_t<T>;
|
|
using reference = range_reference_t<T>;
|
|
using size_type = range_size_t<T>;
|
|
|
|
private:
|
|
T p_range;
|
|
std::size_t p_remaining;
|
|
|
|
public:
|
|
take_range() = delete;
|
|
|
|
take_range(T const &range, std::size_t rem):
|
|
p_range(range), p_remaining(rem)
|
|
{}
|
|
|
|
bool empty() const { return (p_remaining <= 0) || p_range.empty(); }
|
|
|
|
void pop_front() {
|
|
p_range.pop_front();
|
|
if (p_remaining >= 1) {
|
|
--p_remaining;
|
|
}
|
|
}
|
|
|
|
reference front() const { return p_range.front(); }
|
|
};
|
|
|
|
template<typename T>
|
|
struct chunks_range: input_range<chunks_range<T>> {
|
|
using range_category = std::common_type_t<
|
|
range_category_t<T>, forward_range_tag
|
|
>;
|
|
using value_type = take_range<T>;
|
|
using reference = take_range<T>;
|
|
using size_type = range_size_t<T>;
|
|
|
|
private:
|
|
T p_range;
|
|
std::size_t p_chunksize;
|
|
|
|
public:
|
|
chunks_range() = delete;
|
|
|
|
chunks_range(T const &range, std::size_t chs):
|
|
p_range(range), p_chunksize(chs)
|
|
{}
|
|
|
|
bool empty() const { return p_range.empty(); }
|
|
|
|
void pop_front() {
|
|
range_pop_front_n(p_range, range_size_t<T>(p_chunksize));
|
|
}
|
|
|
|
reference front() const { return p_range.take(p_chunksize); }
|
|
};
|
|
|
|
template<std::size_t I, std::size_t N, typename T>
|
|
inline void join_range_pop(T &tup) {
|
|
if constexpr(I != N) {
|
|
if (!std::get<I>(tup).empty()) {
|
|
std::get<I>(tup).pop_front();
|
|
return;
|
|
}
|
|
join_range_pop<I + 1, N>(tup);
|
|
}
|
|
}
|
|
|
|
template<std::size_t I, std::size_t N, typename T>
|
|
inline auto join_range_front(T &tup) {
|
|
if constexpr(I != N) {
|
|
if (!std::get<I>(tup).empty()) {
|
|
return std::get<I>(tup).front();
|
|
}
|
|
return join_range_front<I + 1, N>(tup);
|
|
}
|
|
/* fallback, will probably throw */
|
|
return std::get<0>(tup).front();
|
|
}
|
|
|
|
template<typename ...R>
|
|
struct join_range: input_range<join_range<R...>> {
|
|
using range_category = std::common_type_t<
|
|
forward_range_tag, range_category_t<R>...
|
|
>;
|
|
using value_type = std::common_type_t<range_value_t<R>...>;
|
|
using reference = std::common_type_t<range_reference_t<R>...>;
|
|
using size_type = std::common_type_t<range_size_t<R>...>;
|
|
|
|
private:
|
|
std::tuple<R...> p_ranges;
|
|
|
|
public:
|
|
join_range() = delete;
|
|
|
|
join_range(R const &...ranges): p_ranges(ranges...) {}
|
|
|
|
bool empty() const {
|
|
return std::apply([](auto const &...args) {
|
|
return (... && args.empty());
|
|
}, p_ranges);
|
|
}
|
|
|
|
void pop_front() {
|
|
join_range_pop<0, sizeof...(R)>(p_ranges);
|
|
}
|
|
|
|
reference front() const {
|
|
return join_range_front<0, sizeof...(R)>(p_ranges);
|
|
}
|
|
};
|
|
|
|
template<typename ...T>
|
|
struct zip_value_type {
|
|
using type = std::tuple<T...>;
|
|
};
|
|
|
|
template<typename T, typename U>
|
|
struct zip_value_type<T, U> {
|
|
using type = std::pair<T, U>;
|
|
};
|
|
|
|
template<typename ...T>
|
|
using zip_value_t = typename detail::zip_value_type<T...>::type;
|
|
|
|
template<typename ...R>
|
|
struct zip_range: input_range<zip_range<R...>> {
|
|
using range_category = std::common_type_t<
|
|
forward_range_tag, range_category_t<R>...
|
|
>;
|
|
using value_type = zip_value_t<range_value_t<R>...>;
|
|
using reference = zip_value_t<range_reference_t<R>...>;
|
|
using size_type = std::common_type_t<range_size_t<R>...>;
|
|
|
|
private:
|
|
std::tuple<R...> p_ranges;
|
|
|
|
public:
|
|
zip_range() = delete;
|
|
|
|
zip_range(R const &...ranges): p_ranges(ranges...) {}
|
|
|
|
bool empty() const {
|
|
return std::apply([](auto const &...args) {
|
|
return (... || args.empty());
|
|
}, p_ranges);
|
|
}
|
|
|
|
void pop_front() {
|
|
std::apply([](auto &...args) {
|
|
(args.pop_front(), ...);
|
|
}, p_ranges);
|
|
}
|
|
|
|
reference front() const {
|
|
return std::apply([](auto &&...args) {
|
|
return reference{args.front()...};
|
|
}, p_ranges);
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
struct appender_range: output_range<appender_range<T>> {
|
|
using value_type = typename T::value_type;
|
|
|
|
appender_range(): p_data() {}
|
|
|
|
appender_range(T const &v): p_data(v) {}
|
|
appender_range(T &&v): p_data(std::move(v)) {}
|
|
|
|
appender_range &operator=(T const &v) {
|
|
p_data = v;
|
|
return *this;
|
|
}
|
|
|
|
appender_range &operator=(T &&v) {
|
|
p_data = std::move(v);
|
|
return *this;
|
|
}
|
|
|
|
void clear() { p_data.clear(); }
|
|
bool empty() const { return p_data.empty(); }
|
|
|
|
void reserve(typename T::size_type cap) { p_data.reserve(cap); }
|
|
void resize(typename T::size_type len) { p_data.resize(len); }
|
|
|
|
typename T::size_type size() const { return p_data.size(); }
|
|
typename T::size_type capacity() const { return p_data.capacity(); }
|
|
|
|
void put(typename T::const_reference v) {
|
|
p_data.push_back(v);
|
|
}
|
|
|
|
void put(typename T::value_type &&v) {
|
|
p_data.push_back(std::move(v));
|
|
}
|
|
|
|
T &get() & { return p_data; }
|
|
T const &get() const & { return p_data; }
|
|
|
|
T &&get() && { return std::move(p_data); }
|
|
T const &&get() const && { return std::move(p_data); }
|
|
|
|
private:
|
|
T p_data;
|
|
};
|
|
} /* namespace detail */
|
|
|
|
/** @brief Creates am appender output range for a container.
|
|
*
|
|
* An appender is a kind of output range which wraps a standard container
|
|
* that supports `push_back(v)` and appends to it on every `put(v)`. The
|
|
* value type of the range is the value type of the container.
|
|
*
|
|
* It's posssible to assign a new container to it by copy or move, as long
|
|
* as the container is of the same type. It's also possible to create a new
|
|
* appender from an existing container by copy or move, using
|
|
* ostd::appender(Container &&).
|
|
*
|
|
* Besides implementing a standard output range, appenders also implement
|
|
* an interface to the container, particularly the `clear()`, `empty()`,
|
|
* `reserve(capacity)`, `resize(length)`, `size()` and `capacity()` methods.
|
|
* These are available depending on if they're also available in the wrapped
|
|
* container and have identical signatures.
|
|
*
|
|
* The `get()` method returns an lvalue or rvalue reference to the wrapped
|
|
* container, depending on the lvalue-ness or rvalue-ness of the appender
|
|
* itself (it's ref-qualified). If the appender is const, so will be the
|
|
* returned value.
|
|
*
|
|
* The `put(v)` method is overloaded for both by-copy and by-move put.
|
|
*
|
|
* @see ostd::appender(Container &&)
|
|
*/
|
|
template<typename Container>
|
|
inline auto appender() {
|
|
return detail::appender_range<Container>();
|
|
}
|
|
|
|
/** @brief Creates an appender from an existing container.
|
|
*
|
|
* Like ostd::appender(), except uses an existing container to create the
|
|
* range. The container is passed by universal reference, so it will work
|
|
* for lvalues and rvalues and appropriately copy or move the container.
|
|
*
|
|
* @see ostd::appender()
|
|
*/
|
|
template<typename Container>
|
|
inline auto appender(Container &&v) {
|
|
return detail::appender_range<
|
|
std::remove_cv_t<std::remove_reference_t<Container>>
|
|
>(std::forward<Container>(v));
|
|
}
|
|
|
|
namespace detail {
|
|
template<typename>
|
|
struct iterator_range_tag_base {
|
|
/* fallback, the most basic range */
|
|
using type = input_range_tag;
|
|
};
|
|
|
|
template<>
|
|
struct iterator_range_tag_base<std::output_iterator_tag> {
|
|
using type = output_range_tag;
|
|
};
|
|
|
|
template<>
|
|
struct iterator_range_tag_base<std::forward_iterator_tag> {
|
|
using type = forward_range_tag;
|
|
};
|
|
|
|
template<>
|
|
struct iterator_range_tag_base<std::bidirectional_iterator_tag> {
|
|
using type = bidirectional_range_tag;
|
|
};
|
|
|
|
template<>
|
|
struct iterator_range_tag_base<std::random_access_iterator_tag> {
|
|
using type = finite_random_access_range_tag;
|
|
};
|
|
}
|
|
|
|
/** @brief Gets the best range category that can be created from the given
|
|
* iterator category.
|
|
*
|
|
* They match up to bidirectional. Random access iterators create finite
|
|
* random access ranges. Contiguous ranges can not be created from generic
|
|
* iterator types.
|
|
*/
|
|
template<typename T>
|
|
using iterator_range_tag = typename detail::iterator_range_tag_base<T>::type;
|
|
|
|
/** @brief A range type made up of two iterators.
|
|
*
|
|
* Any iterator type is allowed. If the iterator type is a pointer, which
|
|
* is also a perfectly valid iterator type, the range is contiguous.
|
|
* Otherwise the range category is determined with ostd::iterator_range_tag.
|
|
*/
|
|
template<typename T>
|
|
struct iterator_range: input_range<iterator_range<T>> {
|
|
using range_category = std::conditional_t<
|
|
std::is_pointer_v<T>,
|
|
contiguous_range_tag,
|
|
iterator_range_tag<typename std::iterator_traits<T>::iterator_category>
|
|
>;
|
|
using value_type = typename std::iterator_traits<T>::value_type;
|
|
using reference = typename std::iterator_traits<T>::reference;
|
|
using size_type = std::make_unsigned_t<
|
|
typename std::iterator_traits<T>::difference_type
|
|
>;
|
|
|
|
/** @brief Creates an iterator range.
|
|
*
|
|
* The behavior is undefined if the iterators aren't a valid pair.
|
|
*/
|
|
iterator_range(T beg = T{}, T end = T{}): p_beg(beg), p_end(end) {}
|
|
|
|
/** @brief Converts a compatible pointer iterator range into another.
|
|
*
|
|
* This constructor is only expanded if this iterator range is over
|
|
* a pointer, the other iterator range is also over a pointer and the
|
|
* other's poiner is convertible to this one's pointer.
|
|
*/
|
|
template<typename U, typename = std::enable_if_t<
|
|
std::is_pointer_v<T> && std::is_pointer_v<U> &&
|
|
std::is_convertible_v<U, T>
|
|
>>
|
|
iterator_range(iterator_range<U> const &v):
|
|
p_beg(&v[0]), p_end(&v[v.size()])
|
|
{}
|
|
|
|
/** @brief Effectively true if the iterators are equal. */
|
|
bool empty() const { return p_beg == p_end; }
|
|
|
|
/** @brief Increments the front iterator.
|
|
*
|
|
* If the wrapped iterator is a pointer and the range is empty,
|
|
* std::out_of_range is thrown.
|
|
*/
|
|
void pop_front() {
|
|
/* rely on iterators to do their own checks */
|
|
if constexpr(std::is_pointer_v<T>) {
|
|
if (p_beg == p_end) {
|
|
throw std::out_of_range{"pop_front on empty range"};
|
|
}
|
|
}
|
|
++p_beg;
|
|
}
|
|
|
|
/** @brief Dereferences the beginning iterator. */
|
|
reference front() const { return *p_beg; }
|
|
|
|
/** @brief Decrements the back iterator.
|
|
*
|
|
* If the wrapped iterator is a pointer and the range is empty,
|
|
* std::out_of_range is thrown. Only valid if the range is at
|
|
* least bidirectional.
|
|
*/
|
|
void pop_back() {
|
|
/* rely on iterators to do their own checks */
|
|
if constexpr(std::is_pointer_v<T>) {
|
|
if (p_end == p_beg) {
|
|
throw std::out_of_range{"pop_back on empty range"};
|
|
}
|
|
}
|
|
--p_end;
|
|
}
|
|
|
|
/** @brief Dereferences the decremented end iterator.
|
|
*
|
|
* Effectively `*(end - 1)`. Only valid if the range is at least
|
|
* bidirectional.
|
|
*/
|
|
reference back() const { return *(p_end - 1); }
|
|
|
|
/** @brief Gets the size of the range.
|
|
*
|
|
* Only valid if the range is at least finite random access.
|
|
*/
|
|
size_type size() const { return size_type(p_end - p_beg); }
|
|
|
|
/** @brief Slices the range.
|
|
*
|
|
* Only valid if the range is at least finite random access.
|
|
* If the indexes are not within bounds, the behavior is undefined.
|
|
*/
|
|
iterator_range slice(size_type start, size_type end) const {
|
|
using DT = typename std::iterator_traits<T>::difference_type;
|
|
return iterator_range(p_beg + DT(start), p_beg + DT(end));
|
|
}
|
|
|
|
/** @brief Slices the range with size() for the second argument.
|
|
*
|
|
* Only valid if the range is at least finite random access.
|
|
* If the index is not within bounds, the behavior is undefined.
|
|
*/
|
|
iterator_range slice(size_type start) const {
|
|
return slice(start, size());
|
|
}
|
|
|
|
/** @brief Indexes the range (beginning iterator).
|
|
*
|
|
* Only valid if the range is at least finite random access.
|
|
* If the index is not within bounds, the behavior is undefined.
|
|
*/
|
|
reference operator[](size_type i) const {
|
|
using DT = typename std::iterator_traits<T>::difference_type;
|
|
return p_beg[DT(i)];
|
|
}
|
|
|
|
/** @brief Gets the pointer to the first element.
|
|
*
|
|
* Only valid/useful if the range is contiguous.
|
|
*/
|
|
value_type *data() { return &front(); }
|
|
|
|
/** @brief Gets the pointer to the first element.
|
|
*
|
|
* Only valid/useful if the range is contiguous.
|
|
*/
|
|
value_type const *data() const { return &front(); }
|
|
|
|
/** @brief Assigns a copy of `v` to front and pops it out.
|
|
*
|
|
* Only valid if the iterator is mutable. If the iterator is
|
|
* a pointer, std::out_of_range is thrown if the range is empty.
|
|
*/
|
|
void put(value_type const &v) {
|
|
/* rely on iterators to do their own checks */
|
|
if constexpr(std::is_pointer_v<T>) {
|
|
if (p_beg == p_end) {
|
|
throw std::out_of_range{"put into an empty range"};
|
|
}
|
|
}
|
|
*(p_beg++) = v;
|
|
}
|
|
|
|
/** @brief Moves `v` to front and pops it out.
|
|
*
|
|
* Only valid if the iterator is mutable. If the iterator is
|
|
* a pointer, std::out_of_range is thrown if the range is empty.
|
|
*/
|
|
void put(value_type &&v) {
|
|
if constexpr(std::is_pointer_v<T>) {
|
|
if (p_beg == p_end) {
|
|
throw std::out_of_range{"put into an empty range"};
|
|
}
|
|
}
|
|
*(p_beg++) = std::move(v);
|
|
}
|
|
|
|
private:
|
|
T p_beg, p_end;
|
|
};
|
|
|
|
/** @brief A specialization of ostd::ranged_traits for initializer list types.
|
|
*
|
|
* Sadly, this will not be picked up by the type system when you try to use
|
|
* ostd::iter() directly, so that is also overloaded for initializer lists
|
|
* below.
|
|
*/
|
|
template<typename T>
|
|
struct ranged_traits<std::initializer_list<T>> {
|
|
/** @brief The range type. */
|
|
using range = iterator_range<T const *>;
|
|
|
|
/** @brief Creates the range. */
|
|
static range iter(std::initializer_list<T> il) {
|
|
return range{il.begin(), il.end()};
|
|
}
|
|
};
|
|
|
|
/** @brief An overload of ostd::iter() for initializer lists.
|
|
*
|
|
* This must be done to allow the type system to pick up on expressions
|
|
* such as `ostd::iter({ a, b, c, d, ... })`. It will not do so automatically
|
|
* because it looks for std::initializer_list in the prototype.
|
|
*/
|
|
template<typename T>
|
|
iterator_range<T const *> iter(std::initializer_list<T> init) noexcept {
|
|
return iterator_range<T const *>(init.begin(), init.end());
|
|
}
|
|
|
|
/** @brief An overload of ostd::citer() for initializer lists.
|
|
*
|
|
* This must be done to allow the type system to pick up on expressions
|
|
* such as `ostd::iter({ a, b, c, d, ... })`. It will not do so automatically
|
|
* because it looks for std::initializer_list in the prototype.
|
|
*/
|
|
template<typename T>
|
|
iterator_range<T const *> citer(std::initializer_list<T> init) noexcept {
|
|
return iterator_range<T const *>(init.begin(), init.end());
|
|
}
|
|
|
|
/** @brief A specialization of ostd::ranged_traits for static arrays.
|
|
*
|
|
* This allows iteration of static arrays directly with ostd::iter().
|
|
*/
|
|
template<typename T, std::size_t N>
|
|
struct ranged_traits<T[N]> {
|
|
/** @brief The range type. */
|
|
using range = iterator_range<T *>;
|
|
|
|
/** @brief Creates the range. */
|
|
static range iter(T (&array)[N]) {
|
|
return range{array, array + N};
|
|
}
|
|
};
|
|
|
|
/** @brief Creates an ostd::iterator_range from two pointers.
|
|
*
|
|
* The resulting iterator range will be contiguous. The length of the
|
|
* resulting range will be `b - a`.
|
|
*/
|
|
template<typename T>
|
|
inline iterator_range<T *> iter(T *a, T *b) {
|
|
return iterator_range<T *>(a, b);
|
|
}
|
|
|
|
/* iter on standard containers */
|
|
|
|
namespace detail {
|
|
/* std iter is available, but at the same time direct iter is not */
|
|
template<typename C>
|
|
struct ranged_traits_core<C, false, true> {
|
|
using range = iterator_range<decltype(std::begin(std::declval<C &>()))>;
|
|
|
|
static range iter(C &r) {
|
|
return range{r.begin(), r.end()};
|
|
}
|
|
};
|
|
}
|
|
|
|
/** @brief Creates a `Container` from `range`.
|
|
*
|
|
* Standard sequence containers usually support construction from an
|
|
* iterator pair. This is a utility function that will create a sequence
|
|
* container of the given type using an ostd::input_range's `iter_begin()`
|
|
* and `iter_end()` methods.
|
|
*
|
|
* The remaining arguments are passed after the two iterators.
|
|
*/
|
|
template<typename Container, typename InputRange, typename ...Args>
|
|
inline Container from_range(InputRange range, Args &&...args) {
|
|
return Container(
|
|
range.iter_begin(), range.iter_end(), std::forward<Args>(args)...
|
|
);
|
|
}
|
|
|
|
/** @} */
|
|
|
|
} /* namespace ostd */
|
|
|
|
#undef OSTD_TEST_MODULE
|
|
|
|
#endif
|
|
|
|
/** @} */
|