/** @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 #include #include #include #include #include #include #include #include #include #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 inline auto range_category_test(int) -> std::integral_constant< bool, !std::is_void_v >; template inline std::false_type range_category_test(...); template static inline constexpr bool const test_range_category = decltype(range_category_test(0))::value; template struct range_traits_base { template static constexpr bool is_element_swappable_with = false; static constexpr bool is_element_swappable = false; template static constexpr bool is_element_nothrow_swappable_with = false; static constexpr bool is_element_nothrow_swappable = false; }; template static inline constexpr bool const test_elem_reference = std::is_convertible_v && std::is_lvalue_reference_v && std::is_same_v< typename R::value_type, std::remove_reference_t >; template struct range_traits_base { 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 static constexpr bool is_element_swappable_with = test_elem_reference && test_elem_reference && std::is_swappable_with_v; static constexpr bool is_element_swappable = is_element_swappable_with; template static constexpr bool is_element_nothrow_swappable_with = test_elem_reference && test_elem_reference && std::is_nothrow_swappable_with_v; static constexpr bool is_element_nothrow_swappable = is_element_nothrow_swappable_with; }; template struct range_traits_base { using range_category = typename R::range_category; using value_type = typename R::value_type; template static constexpr bool is_element_swappable_with = false; static constexpr bool is_element_swappable = false; template static constexpr bool is_element_nothrow_swappable_with = false; static constexpr bool is_element_nothrow_swappable = false; }; template struct range_traits_impl { template static constexpr bool is_element_swappable_with = false; static constexpr bool is_element_swappable = false; template static constexpr bool is_element_nothrow_swappable_with = false; static constexpr bool is_element_nothrow_swappable = false; }; template struct range_traits_impl: range_traits_base< R, std::is_convertible_v, std::is_convertible_v > {}; } /** @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 * static constexpr bool is_element_swappable_with = ...; * static constexpr bool is_element_swappable = is_element_swappable_with; * template * static constexpr bool is_element_nothrow_swappable_with = ...; * static constexpr bool is_element_nothrow_swappable = is_element_swappable_with; * ~~~ * * 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`. * * 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> * ~~~ * * 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 struct range_traits: detail::range_traits_impl> {}; /** @brief The category tag of a range type. * * It's the same as doing * * ~~~{.cc} * typename ostd::range_traits::range_category * ~~~ * * Works for all types of ranges. * * @see ostd::range_size_t, ostd::range_value_t, ostd::range_reference_t */ template using range_category_t = typename range_traits::range_category; /** @brief The size type of a range type. * * It's the same as doing * * ~~~{.cc} * typename ostd::range_traits::size_type * ~~~ * * Works only for input-type ranges. * * @see ostd::range_category_t, ostd::range_value_t, ostd::range_reference_t */ template using range_size_t = typename range_traits::size_type; /** @brief The value type of range elements. * * It's the same as doing * * ~~~{.cc} * typename ostd::range_traits::value_type * ~~~ * * Works for all types of ranges. * * @see ostd::range_category_t, ostd::range_size_t, ostd::range_reference_t */ template using range_value_t = typename range_traits::value_type; /** @brief The type returned by range element accessors. * * It's the same as doing * * ~~~{.cc} * typename ostd::range_traits::reference * ~~~ * * Works only for input-type ranges. * * @see ostd::range_category_t, ostd::range_size_t, ostd::range_value_t */ template using range_reference_t = typename range_traits::reference; /** @brief Checks whether two ranges can swap elements with each other. * * It's the same as doing * * ~~~{.cc} * ostd::range_traits::template is_element_swappable_with * ~~~ * */ template static inline constexpr bool const is_range_element_swappable_with = range_traits::template is_element_swappable_with; /** @brief Checks whether a range can swap elements within itself. * * It's the same as doing * * ~~~{.cc} * ostd::range_traits::is_element_swappable * ~~~ * */ template static inline constexpr bool const is_range_element_swappable = range_traits::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::template is_element_nothrow_swappable_with * ~~~ * */ template static inline constexpr bool const is_range_element_nothrow_swappable_with = range_traits::template is_element_nothrow_swappable_with; /** @brief Checks whether a range can nothrow swap elements within itself. * * It's the same as doing * * ~~~{.cc} * ostd::range_traits::is_element_nothrow_swappable * ~~~ * */ template static inline constexpr bool const is_range_element_nothrow_swappable = range_traits::is_element_nothrow_swappable; // is input range namespace detail { template static inline constexpr bool const is_input_range_core = std::is_convertible_v, input_range_tag>; template> static inline constexpr bool const is_input_range_base = false; template static inline constexpr bool const is_input_range_base = detail::is_input_range_core; } /** @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 static inline constexpr bool const is_input_range = detail::is_input_range_base; // is forward range namespace detail { template static inline constexpr bool const is_forward_range_core = std::is_convertible_v, forward_range_tag>; template> static inline constexpr bool const is_forward_range_base = false; template static inline constexpr bool const is_forward_range_base = detail::is_forward_range_core; } /** @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 static inline constexpr bool const is_forward_range = detail::is_forward_range_base; // is bidirectional range namespace detail { template static inline constexpr bool const is_bidirectional_range_core = std::is_convertible_v, bidirectional_range_tag>; template> static inline constexpr bool const is_bidirectional_range_base = false; template static inline constexpr bool const is_bidirectional_range_base = detail::is_bidirectional_range_core; } /** @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 static inline constexpr bool const is_bidirectional_range = detail::is_bidirectional_range_base; // is random access range namespace detail { template static inline constexpr bool const is_random_access_range_core = std::is_convertible_v, random_access_range_tag>; template> static inline constexpr bool const is_random_access_range_base = false; template static inline constexpr bool const is_random_access_range_base = detail::is_random_access_range_core; } /** @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 static inline constexpr bool const is_random_access_range = detail::is_random_access_range_base; // is finite random access range namespace detail { template static inline constexpr bool const is_finite_random_access_range_core = std::is_convertible_v, finite_random_access_range_tag>; template> static inline constexpr bool const is_finite_random_access_range_base = false; template static inline constexpr bool const is_finite_random_access_range_base = detail::is_finite_random_access_range_core; } /** @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 static inline constexpr bool const is_finite_random_access_range = detail::is_finite_random_access_range_base; // is contiguous range namespace detail { template static inline constexpr bool const is_contiguous_range_core = std::is_convertible_v, contiguous_range_tag>; template> static inline constexpr bool const is_contiguous_range_base = false; template static inline constexpr bool const is_contiguous_range_base = detail::is_contiguous_range_core; } /** @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 static inline constexpr bool const is_contiguous_range = detail::is_contiguous_range_base; // is output range namespace detail { template inline auto test_outrange(int) -> typename std::is_void< decltype(std::declval().put(std::declval())) >::tyoe; template inline std::false_type test_outrange(...); template static inline constexpr bool const output_range_test = decltype(test_outrange())::value; template static inline constexpr bool const is_output_range_core = std::is_convertible_v, output_range_tag> || ( is_input_range && ( output_range_test const &> || output_range_test &&> || output_range_test > ) ); template> static inline constexpr bool const is_output_range_base = false; template static inline constexpr bool const is_output_range_base = detail::is_output_range_core; } /** @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 static inline constexpr bool const is_output_range = detail::is_output_range_base; namespace detail { // range iterator template 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 operator*() const { return p_range.front(); } bool operator!=(std::nullptr_t) const { return !p_range.empty(); } private: T p_range; }; // complete range iterator template struct full_range_iterator { using iterator_category = std::input_iterator_tag; using value_type = range_value_t; using reference = range_reference_t; using pointer = value_type *; using difference_type = std::make_signed_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 p_range; }; } namespace detail { template struct reverse_range; template struct move_range; template struct enumerated_range; template struct take_range; template struct chunks_range; template struct join_range; template 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 inline range_size_t range_pop_front_n(IR &range, range_size_t n) { if constexpr(std::is_convertible_v< range_category_t, 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 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 inline range_size_t range_pop_back_n(IR &range, range_size_t n) { if constexpr(std::is_convertible_v< range_category_t, 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 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 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; /** @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(*static_cast(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(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(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, "Wrapped range must be bidirectional or better" ); return detail::reverse_range(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> == true * std::is_same_v< * ostd::range_value_t, * std::remove_reference_t * > == true * ~~~ * * The value and size types remain the same. The new reference * type becomes `ostd::range_value_t &&`. * * Accesses to the front member result in the element being moved. */ auto movable() const { return detail::move_range(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 index; * range_reference_t 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(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(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(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 auto join(R1 r1, RR ...rr) const { return detail::join_range( 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 auto zip(R1 r1, RR ...rr) const { return detail::zip_range( iter(), std::move(r1), std::move(rr)... ); } /** @brief Simpler syntax for accessing the front element. */ auto operator*() const { return std::forward(this)->front())>( static_cast(this)->front() ); } /** @brief Simpler syntax for popping out the front element. */ B &operator++() { static_cast(this)->pop_front(); return *static_cast(this); } /** @brief Simpler syntax for popping out the front element. * * Returns the range before the pop happened. */ B operator++(int) { B tmp(*static_cast(this)); static_cast(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 auto operator|(F &&func) { return func(*static_cast(this)); } /** @brief A necessary overload for pipeable algorithm support. */ template auto operator|(F &&func) const { return func(*static_cast(this)); } /* universal bool operator */ /** @brief Checks if the range is not empty. */ explicit operator bool() const { return !(static_cast(this)->empty()); } }; /** @brief The base type for all pure output ranges. */ template 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 inline void range_put_all(OR &orange, IR range) { for (; !range.empty(); range.pop_front()) { orange.put(range.front()); } } namespace detail { template struct noop_output_range: output_range> { 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 inline auto noop_sink() { return detail::noop_output_range{}; } namespace detail { template struct counting_output_range: output_range> { using value_type = range_value_t; 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 get_written() const; * ~~~ * * These methods are provided to retrieve the range: * * ~~~{.cc} * R &get_range(); * R const &get_range() const; * ~~~ */ template inline auto counting_sink(R const &range) { return detail::counting_output_range{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 inline auto join(R &&range) { return [range = std::forward(range)](auto &obj) mutable { return obj.join(std::forward(range)); }; } /** @brief A pipeable version of ostd::input_range::join(). */ template inline auto join(R1 &&r1, R &&...rr) { return [ ranges = std::forward_as_tuple( std::forward(r1), std::forward(rr)... ) ] (auto &obj) mutable { return std::apply([&obj](auto &&...args) mutable { return obj.join(std::forward(args)...); }, std::move(ranges)); }; } /** @brief A pipeable version of ostd::input_range::zip(). */ template inline auto zip(R &&range) { return [range = std::forward(range)](auto &obj) mutable { return obj.zip(std::forward(range)); }; } /** @brief A pipeable version of ostd::input_range::zip(). */ template inline auto zip(R1 &&r1, R &&...rr) { return [ ranges = std::forward_as_tuple( std::forward(r1), std::forward(rr)... ) ] (auto &obj) mutable { return std::apply([&obj](auto &&...args) mutable { return obj.zip(std::forward(args)...); }, std::move(ranges)); }; } namespace detail { template inline auto test_direct_iter(int) -> std::integral_constant< bool, is_input_range().iter())> >; template inline std::false_type test_direct_iter(...); template static inline constexpr bool const direct_iter_test = decltype(test_direct_iter(0))::value; template inline auto test_std_iter(int) -> std::integral_constant< bool, !std::is_void_v()))> && !std::is_void_v()))> >; template inline std::false_type test_std_iter(...); template static inline constexpr bool const std_iter_test = decltype(test_std_iter(0))::value; /* direct iter and std iter; the case for std iter is * specialized after iterator_range is actually defined */ template struct ranged_traits_core {}; /* direct iter is available, regardless of std iter being available */ template struct ranged_traits_core { using range = decltype(std::declval().iter()); static range iter(C &r) { return r.iter(); } }; template struct ranged_traits_impl: ranged_traits_core< C, direct_iter_test, std_iter_test > {}; } /** @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 struct ranged_traits: detail::ranged_traits_impl {}; /** @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 inline auto iter(T &&r) -> decltype(ranged_traits>::iter(r)) { return ranged_traits>::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 inline auto citer(T const &r) -> decltype(ranged_traits::iter(r)) { return ranged_traits::iter(r); } namespace detail { template inline auto test_iterable(int) -> std::integral_constant< bool, is_input_range()))> >; template inline std::false_type test_iterable(...); template static inline constexpr bool const iterable_test = decltype(test_iterable(0))::value; } namespace detail { template struct number_range: input_range> { 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 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 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 inline auto range(T a, T b, std::make_signed_t step = 1) { return detail::number_range(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 inline auto range(T v) { return detail::number_range(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 struct reverse_range: input_range> { using range_category = std::conditional_t< is_finite_random_access_range, finite_random_access_range_tag, bidirectional_range_tag >; using value_type = range_value_t ; using reference = range_reference_t; using size_type = range_size_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 struct move_range: input_range> { static_assert( std::is_reference_v>, "Wrapped range must return proper references for accessors" ); static_assert( std::is_same_v< range_value_t, std::remove_reference_t> >, "Wrapped range references must be proper references to the value type" ); using range_category = input_range_tag; using value_type = range_value_t; using reference = range_value_t &&; using size_type = range_size_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 struct enumerated_range: input_range> { private: struct enumerated_value_t { range_size_t index; range_reference_t value; }; public: using range_category = std::common_type_t< range_category_t, forward_range_tag >; using value_type = range_value_t; using reference = enumerated_value_t; using size_type = range_size_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 struct take_range: input_range> { using range_category = std::common_type_t< range_category_t, forward_range_tag >; using value_type = range_value_t; using reference = range_reference_t; using size_type = range_size_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 struct chunks_range: input_range> { using range_category = std::common_type_t< range_category_t, forward_range_tag >; using value_type = take_range; using reference = take_range; using size_type = range_size_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(p_chunksize)); } reference front() const { return p_range.take(p_chunksize); } }; template inline void join_range_pop(T &tup) { if constexpr(I != N) { if (!std::get(tup).empty()) { std::get(tup).pop_front(); return; } join_range_pop(tup); } } template inline auto join_range_front(T &tup) { if constexpr(I != N) { if (!std::get(tup).empty()) { return std::get(tup).front(); } return join_range_front(tup); } /* fallback, will probably throw */ return std::get<0>(tup).front(); } template struct join_range: input_range> { using range_category = std::common_type_t< forward_range_tag, range_category_t... >; using value_type = std::common_type_t...>; using reference = std::common_type_t...>; using size_type = std::common_type_t...>; private: std::tuple 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 struct zip_value_type { using type = std::tuple; }; template struct zip_value_type { using type = std::pair; }; template using zip_value_t = typename detail::zip_value_type::type; template struct zip_range: input_range> { using range_category = std::common_type_t< forward_range_tag, range_category_t... >; using value_type = zip_value_t...>; using reference = zip_value_t...>; using size_type = std::common_type_t...>; private: std::tuple 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 struct appender_range: output_range> { 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 inline auto appender() { return detail::appender_range(); } /** @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 inline auto appender(Container &&v) { return detail::appender_range< std::remove_cv_t> >(std::forward(v)); } namespace detail { template struct iterator_range_tag_base { /* fallback, the most basic range */ using type = input_range_tag; }; template<> struct iterator_range_tag_base { using type = output_range_tag; }; template<> struct iterator_range_tag_base { using type = forward_range_tag; }; template<> struct iterator_range_tag_base { using type = bidirectional_range_tag; }; template<> struct iterator_range_tag_base { 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 using iterator_range_tag = typename detail::iterator_range_tag_base::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 struct iterator_range: input_range> { using range_category = std::conditional_t< std::is_pointer_v, contiguous_range_tag, iterator_range_tag::iterator_category> >; using value_type = typename std::iterator_traits::value_type; using reference = typename std::iterator_traits::reference; using size_type = std::make_unsigned_t< typename std::iterator_traits::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 && std::is_pointer_v && std::is_convertible_v >> iterator_range(iterator_range 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) { 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) { 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::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::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) { 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) { 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 struct ranged_traits> { /** @brief The range type. */ using range = iterator_range; /** @brief Creates the range. */ static range iter(std::initializer_list 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 iterator_range iter(std::initializer_list init) noexcept { return iterator_range(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 iterator_range citer(std::initializer_list init) noexcept { return iterator_range(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 struct ranged_traits { /** @brief The range type. */ using range = iterator_range; /** @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 inline iterator_range iter(T *a, T *b) { return iterator_range(a, b); } /* iter on standard containers */ namespace detail { /* std iter is available, but at the same time direct iter is not */ template struct ranged_traits_core { using range = iterator_range()))>; 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 inline Container from_range(InputRange range, Args &&...args) { return Container( range.iter_begin(), range.iter_end(), std::forward(args)... ); } /** @} */ } /* namespace ostd */ #undef OSTD_TEST_MODULE #endif /** @} */