diff --git a/ostd/range.hh b/ostd/range.hh index 7bc2817..7e535a7 100644 --- a/ostd/range.hh +++ b/ostd/range.hh @@ -1,6 +1,37 @@ -/* Ranges for libostd. +/** @defgroup Ranges * - * This file is part of libostd. See COPYING.md for futher information. + * @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. + * + * @{ + */ + +/** @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. + * + * @copyright See COPYING.md in the project tree for further information. */ #ifndef OSTD_RANGE_HH @@ -17,12 +48,110 @@ 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_rnage_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_rnage_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 { @@ -61,21 +190,93 @@ namespace detail { > {}; } +/** @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: + * + * * `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 + * * `difference_type` - the type used for distances (typically `ptrdiff_t`) + * + * 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, ostd::range_difference_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 + * ~~~ + * + * @see ostd::range_size_t, ostd::range_value_t, ostd::range_reference_t, + * ostd::range_difference_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 + * ~~~ + * + * @see ostd::range_category_t, ostd::range_value_t, ostd::range_reference_t, + * ostd::range_difference_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 + * ~~~ + * + * @see ostd::range_category_t, ostd::range_size_t, ostd::range_reference_t, + * ostd::range_difference_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 + * ~~~ + * + * @see ostd::range_category_t, ostd::range_size_t, ostd::range_value_t, + * ostd::range_difference_t + */ template using range_reference_t = typename range_traits::reference; +/** @brief The difference type of a range type. + * + * It's the same as doing + * + * ~~~{.cc} + * typename ostd::range_traits::difference_type + * ~~~ + * + * @see ostd::range_category_t, ostd::range_size_t, ostd::range_value_t, + * ostd::range_reference_t + */ template using range_difference_t = typename range_traits::difference_type; @@ -93,6 +294,13 @@ namespace detail { constexpr bool 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 constexpr bool is_input_range = detail::is_input_range_base; @@ -110,6 +318,13 @@ namespace detail { constexpr bool 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 constexpr bool is_forward_range = detail::is_forward_range_base; @@ -128,6 +343,13 @@ namespace detail { 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 constexpr bool is_bidirectional_range = detail::is_bidirectional_range_base; @@ -146,6 +368,14 @@ namespace detail { 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 constexpr bool is_random_access_range = detail::is_random_access_range_base; @@ -164,14 +394,17 @@ namespace detail { 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 constexpr bool is_finite_random_access_range = detail::is_finite_random_access_range_base; -// is infinite random access range - -template constexpr bool is_infinite_random_access_range = - is_random_access_range && !is_finite_random_access_range; - // is contiguous range namespace detail { @@ -187,6 +420,12 @@ namespace detail { 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 constexpr bool is_contiguous_range = detail::is_contiguous_range_base; @@ -221,6 +460,15 @@ namespace detail { constexpr bool 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 constexpr bool is_output_range = detail::is_output_range_base; @@ -261,6 +509,22 @@ 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< @@ -280,6 +544,23 @@ inline range_size_t range_pop_front_n(IR &range, range_size_t n) { } } +/** @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< @@ -299,61 +580,124 @@ inline range_size_t range_pop_back_n(IR &range, range_size_t n) { } } +/** @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 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(). + */ detail::range_iterator begin() const { return detail::range_iterator(*static_cast(this)); } + + /** @brief Gets a sentinel for begin(). */ std::nullptr_t end() const { return nullptr; } + /** @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. + * + * The current range must be at least bidirectional. + */ reverse_range reverse() const { return reverse_range(iter()); } + /** @brief Gets a wrapper range that moves all the elements. + * + * Any range type works as long as its ostd::range_reference_t is + * a proper reference type for its ostd::range_value_t. + */ move_range movable() const { return move_range(iter()); } + /** @brief Gets an enumerated range for the range. + * + * This one allows you to iterate over ranges using the range-based + * for loop and still have access to current index of the element. + */ enumerated_range enumerate() const { return enumerated_range(iter()); } + /** @brief Gets a range representing several elements of the range. + * + * Wraps the range but only at most `n` elements. + */ template take_range take(Size n) const { return take_range(iter(), n); } + /** @brief Splits the range into range of chunks. + * + * Each element of the range will be like take() with `n`. + */ template chunks_range chunks(Size n) const { return 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. + */ template join_range join(R1 r1, RR ...rr) const { return 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. + */ template zip_range zip(R1 r1, RR ...rr) const { return 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(); @@ -362,18 +706,25 @@ struct input_range { /* 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)); } + + /** @brief A necessary overload for pipeable algorithm support. */ template auto operator|(F &&func) && { return func(std::move(*static_cast(this))); } + + /** @brief A necessary overload for pipeable algorithm support. */ template auto operator|(F &&func) const && { return func(std::move(*static_cast(this))); @@ -381,16 +732,26 @@ struct input_range { /* 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()) { @@ -398,6 +759,12 @@ inline void range_put_all(OR &orange, IR range) { } } +/** @brief An output range type that does nothing. + * + * Represents a generic sink that doesn't store the values anywhere. Can + * be used in metaprogramming and you need to just get rid of a portion + * of some range. + */ template struct noop_output_range: output_range> { using value_type = T; @@ -405,9 +772,16 @@ struct noop_output_range: output_range> { using size_type = std::size_t; using difference_type = std::ptrdiff_t; + /** @brief Has no effect. */ void put(T const &) {} }; +/** @brief A wrapper range that counts the elements put in the range. + * + * Takes any output range and increments a counter each time a value is + * put into it. This is useful if you need to check how many elements + * were actaully written into an output range from the inside of a call. + */ template struct counting_output_range: output_range> { using value_type = range_value_t; @@ -420,50 +794,64 @@ private: size_type p_written = 0; public: + /** @brief A range to wrap is needed. */ counting_output_range() = delete; + + /** @brief Constructs the range from an existing range. */ counting_output_range(R const &range): p_range(range) {} + /** @brief Copies a value into the wrapped range and increments. */ void put(value_type const &v) { p_range.put(v); ++p_written; } + + /** @brief Moves a vallue into the wrapped range and increments. */ void put(value_type &&v) { p_range.put(std::move(v)); ++p_written; } + /** @brief Gets the number of written values. */ size_type get_written() const { return p_written; } }; +/** @brief A simple constructor function for ostd::counting_output_range. */ template inline counting_output_range range_counter(R const &range) { return 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(). */ template inline auto take(T n) { return [n](auto &&obj) { return obj.take(n); }; } +/** @brief A pipeable version of ostd::input_range::chunks(). */ template inline auto chunks(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 { @@ -471,6 +859,7 @@ inline auto join(R &&range) { }; } +/** @brief A pipeable version of ostd::input_range::join(). */ template inline auto join(R1 &&r1, R &&...rr) { return [ @@ -484,6 +873,7 @@ inline auto join(R1 &&r1, R &&...rr) { }; } +/** @brief A pipeable version of ostd::input_range::zip(). */ template inline auto zip(R &&range) { return [range = std::forward(range)](auto &&obj) mutable { @@ -491,6 +881,7 @@ inline auto zip(R &&range) { }; } +/** @brief A pipeable version of ostd::input_range::zip(). */ template inline auto zip(R1 &&r1, R &&...rr) { return [ @@ -548,9 +939,32 @@ namespace detail { > {}; } +/** @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)) @@ -558,6 +972,15 @@ inline auto iter(T &&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); @@ -573,10 +996,29 @@ namespace detail { constexpr bool iterable_test = decltype(test_iterable(0))::value; } +/** @brief A wrapper range that lazily reverses direction. + * + * The range it wraps must be at least bidirectional. It re-routes the + * calls so that accessors to the back access the front and vice versa. + * + * The category depends on the range it wraps, it can be either + * ostd::bidirectional_range_tag or ostd::finite_random_access_range_tag. + * Infinite random access ranges become bidirectional because there is no + * way to index from the other side if the size is unknown. + * + * It will fail to compile of the wrapped type is not at least bidirectional. + */ template struct reverse_range: input_range> { - using range_category = std::common_type_t< - range_category_t, finite_random_access_range_tag + static_assert( + is_bidirectional_range, + "Wrapped range must be bidirectional or better" + ); + + 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 ; @@ -587,53 +1029,79 @@ private: T p_range; public: + /** @brief Not default constructible. */ reverse_range() = delete; - reverse_range(T const &range): p_range(range) {} - reverse_range(reverse_range const &it): p_range(it.p_range) {} - reverse_range(reverse_range &&it): p_range(std::move(it.p_range)) {} - reverse_range &operator=(reverse_range const &v) { - p_range = v.p_range; - return *this; - } - reverse_range &operator=(reverse_range &&v) { - p_range = std::move(v.p_range); - return *this; - } + /** @brief Constructs a reverse range from a range. */ + reverse_range(T const &v): p_range(v) {} + + /** @brief Assignss a reverse range from a wrapped range type. */ reverse_range &operator=(T const &v) { p_range = v; return *this; } - reverse_range &operator=(T &&v) { - p_range = std::move(v); - return *this; - } + /** @brief Checks for emptiness of the wrapped range. */ bool empty() const { return p_range.empty(); } + + /** @brief Checks for size of the wrapped range. */ size_type size() const { return p_range.size(); } + /** @brief Pops out the back element of the wrapped range. */ void pop_front() { p_range.pop_back(); } + + /** @brief Pops out the front element of the wrapped range. */ void pop_back() { p_range.pop_front(); } + /** @brief Accesses the back of the wrapped range. */ reference front() const { return p_range.back(); } + + /** @brief Accesses the front of the wrapped range. */ reference back() const { return p_range.front(); } + /** @brief Accesses the elements starting from the back. */ reference operator[](size_type i) const { return p_range[size() - i - 1]; } + /** @brief Slices the range starting from the back. + * + * The indexes must be within bounds or the behavior is undefined. + */ 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)}; } + + /** @brief Like slice() with size() for the second argument. */ reverse_range slice(size_type start) const { return slice(start, size()); } }; +/** @brief A wrapper range that moves the elements on access. + * + * The range is an input range and each element is safe to access exactly + * once, as the range will move the elements on access. This type is not + * useful for use in algorithms but rather when you need to move elements + * of some range into a container or something similar. + * + * The ostd::range_reference_t of `T` must be a proper reference (lvalue or + * rvalue kind) to `T`'s ostd::range_value_t. This is ensured at compile time + * so compilation will fail if it's not. + */ template struct move_range: input_range> { - using range_category = std::common_type_t< - range_category_t, finite_random_access_range_tag - >; + 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 ; @@ -643,50 +1111,38 @@ private: T p_range; public: + /** @brief Not default constructible. */ move_range() = delete; - move_range(T const &range): p_range(range) {} - move_range(move_range const &it): p_range(it.p_range) {} - move_range(move_range &&it): p_range(std::move(it.p_range)) {} - move_range &operator=(move_range const &v) { - p_range = v.p_range; - return *this; - } - move_range &operator=(move_range &&v) { - p_range = std::move(v.p_range); - return *this; - } + /** @brief Constructs a move range from a range. */ + move_range(T const &range): p_range(range) {} + + /** @brief Assignss a reverse range from a wrapped range type. */ move_range &operator=(T const &v) { p_range = v; return *this; } - move_range &operator=(T &&v) { - p_range = std::move(v); - return *this; - } + /** @brief Checks for emptiness of the wrapped range. */ bool empty() const { return p_range.empty(); } - size_type size() const { return p_range.size(); } + /** @brief Pops out the front value of the wrapped range. */ void pop_front() { p_range.pop_front(); } - void pop_back() { p_range.pop_back(); } + /** @brief Moves the front element of the wrapped range. */ reference front() const { return std::move(p_range.front()); } - reference back() const { return std::move(p_range.back()); } - - reference operator[](size_type i) const { return std::move(p_range[i]); } - - move_range slice(size_type start, size_type end) const { - return move_range{p_range.slice(start, end)}; - } - move_range slice(size_type start) const { - return slice(start, size()); - } - - void put(value_type const &v) { p_range.put(v); } - void put(value_type &&v) { p_range.put(std::move(v)); } }; +/** @brief A simple forward range to count between a range of integers. + * + * This type exists mostly to allow using range-based for loop to count + * between two integer values. The interval it represents is half open + * and it supports step. Negative numbers and interation are also supported. + * + * This is also an example of how values can be used as reference type for + * a range. This will threfore not work with all algorithms but it doesn't + * need to, it mostly has a single purpose anyway. + */ template struct number_range: input_range> { using range_category = forward_range_tag; @@ -695,46 +1151,99 @@ struct number_range: input_range> { using size_type = std::size_t; using difference_type = std::ptrdiff_t; + /** @brief Not default constructible. */ number_range() = delete; - number_range(T a, T b, T step = T(1)): + + /** @brief Constructs the range from inputs. + * + * @param[in] a The starting value. + * @param[in] b The ending value plus 1. + * @param[in] step By how much to increment each time. + */ + number_range(T a, T b, std::make_signed_t step = 1): p_a(a), p_b(b), p_step(step) {} + + /** @brief Uses 0 as a start, `v` as the end and `1` as the step. */ number_range(T v): p_a(0), p_b(v), p_step(1) {} + /** @brief Checks if the range has reached its end. */ bool empty() const { return p_a * p_step >= p_b * p_step; } + /** @brief Increments the current state by step. */ void pop_front() { p_a += p_step; } - T front() const { return p_a; } + + /** @brief Gets the current state. */ + reference front() const { return p_a; } private: - T p_a, p_b, p_step; + T p_a, p_b; + std::make_signed_t p_step; }; +/** @brief A simple constructing function for ostd::number_range. + * + * This allows writing nice code such as + * + * ~~~{.cc} + * for (int i: ostd::range(5, 10)) { // from 5 to 10, not including 10 + * ... + * } + * ~~~ + */ template -inline number_range range(T a, T b, T step = T(1)) { +inline number_range range(T a, T b, std::make_signed_t step = 1) { return number_range(a, b, step); } +/** @brief A simple constructing function for ostd::number_range. + * + * This allows writing nice code such as + * + * ~~~{.cc} + * for (int i: ostd::range(10)) { //from 0 to 10, not including 10 + * ... + * } + * ~~~ + */ template inline number_range range(T v) { return number_range(v); } -template -struct enumerated_value_t { - S index; - T value; -}; - +/** @brief A wrapper range that allows access to current iterating index. + * + * The range is input or forward depending on what it wraps. There reference + * type here is a structure which wraps the actual reference from the range + * (as the `value` member) and the index of the value (as the `index` member, + * which is of type `ostd::range_size_t`). The main use for this is to + * be able to use range-based for loop with ranges and still have access + * to the index, starting with 0. + * + * A simple example would be: + * + * ~~~{.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); + * } + * ~~~ + */ 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< - range_reference_t, range_size_t - >; + using reference = enumerated_value_t; using size_type = range_size_t ; using difference_type = range_difference_t; @@ -743,51 +1252,47 @@ private: size_type p_index; public: + /** @brief Not default constructible. */ enumerated_range() = delete; + /** @brief Constructs an enumerated range from a range. */ enumerated_range(T const &range): p_range(range), p_index(0) {} - enumerated_range(enumerated_range const &it): - p_range(it.p_range), p_index(it.p_index) - {} - - enumerated_range(enumerated_range &&it): - p_range(std::move(it.p_range)), p_index(it.p_index) - {} - - enumerated_range &operator=(enumerated_range const &v) { - p_range = v.p_range; - p_index = v.p_index; - return *this; - } - enumerated_range &operator=(enumerated_range &&v) { - p_range = std::move(v.p_range); - p_index = v.p_index; - return *this; - } + /** @brief Assignss an enumerated range from a wrapped range type. + * + * The index is reset to 0. + */ enumerated_range &operator=(T const &v) { p_range = v; p_index = 0; return *this; } - enumerated_range &operator=(T &&v) { - p_range = std::move(v); - p_index = 0; - return *this; - } + /** @brief Checks the underlying range for emptiness. */ bool empty() const { return p_range.empty(); } + /** @brief Pops out the front element of the range and increments. */ void pop_front() { p_range.pop_front(); ++p_index; } + /** @brief Returns the wrapped reference with an index. */ reference front() const { return reference{p_index, p_range.front()}; } }; +/** @brief A wrapper range that allows access to some number of elements. + * + * The range is input or forward depending on what it wraps. The accesses + * go through the wrapped range, but it also keeps a counter of how many + * elements are remaining in the range and when it runs out it stops allowing + * any more accesses. + * + * If the wrapped range runs out sooner than the number of allowed elements, + * it will be considered empty anyway. + */ template struct take_range: input_range> { using range_category = std::common_type_t< @@ -803,28 +1308,25 @@ private: size_type p_remaining; public: + /** @brief Not default constructible. */ take_range() = delete; - take_range(T const &range, range_size_t rem): + + /** @brief Constructs the range with some number of elements. */ + take_range(T const &range, size_type rem): p_range(range), p_remaining(rem) {} - take_range(take_range const &it): - p_range(it.p_range), p_remaining(it.p_remaining) - {} - take_range(take_range &&it): - p_range(std::move(it.p_range)), p_remaining(it.p_remaining) - {} - - take_range &operator=(take_range const &v) { - p_range = v.p_range; p_remaining = v.p_remaining; return *this; - } - take_range &operator=(take_range &&v) { - p_range = std::move(v.p_range); - p_remaining = v.p_remaining; - return *this; - } + /** @brief Checks if there are any no more elements left. + * + * The range is not considered empty when the internal remaining counter + * is larger than zero and the underlying range is not empty. + */ bool empty() const { return (p_remaining <= 0) || p_range.empty(); } + /** @brief Pops out the front element. + * + * The behavior is undefined when empty() is true. + */ void pop_front() { p_range.pop_front(); if (p_remaining >= 1) { @@ -832,9 +1334,16 @@ public: } } + /** @brief Accesses the front element. */ reference front() const { return p_range.front(); } }; +/** @brief A wrapper range that splits a range into evenly sized chunks. + * + * The resulting range is either input or forward. Each element of the range + * is an ostd::take_range with at most the given number of elements. If the + * wrapped range's size is not a multiple, the last chunk will be smaller. + */ template struct chunks_range: input_range> { using range_category = std::common_type_t< @@ -850,32 +1359,26 @@ private: size_type p_chunksize; public: + /** @brief Not default constructible. */ chunks_range() = delete; + + /** @brief Constructs the range with some number of elements per chunk. */ chunks_range(T const &range, range_size_t chs): p_range(range), p_chunksize(chs) {} - chunks_range(chunks_range const &it): - p_range(it.p_range), p_chunksize(it.p_chunksize) - {} - chunks_range(chunks_range &&it): - p_range(std::move(it.p_range)), p_chunksize(it.p_chunksize) - {} - - chunks_range &operator=(chunks_range const &v) { - p_range = v.p_range; p_chunksize = v.p_chunksize; return *this; - } - chunks_range &operator=(chunks_range &&v) { - p_range = std::move(v.p_range); - p_chunksize = v.p_chunksize; - return *this; - } + /** @brief Checks if the underlying range is empty. */ bool empty() const { return p_range.empty(); } + /** @brief Pops out some elements from the underlying range. + * + * The number is up to the chunk size. + */ void pop_front() { range_pop_front_n(p_range, p_chunksize); } + /** @brief Creates a chunk from the front elements. */ reference front() const { return p_range.take(p_chunksize); } }; @@ -904,6 +1407,13 @@ namespace detail { } } +/** @brief A wrapper range that joins multiple ranges together. + * + * The ranges don't have to be the same. However, their types must be + * compatible. The vlaue type, reference, size type and difference type + * are all common types between all the ranges; the category is at most + * forward range but can be input if any of the ranges is input. + */ template struct join_range: input_range> { using range_category = std::common_type_t< @@ -918,32 +1428,28 @@ private: std::tuple p_ranges; public: + /** @brief Not default constructible. */ join_range() = delete; + + /** @brief Constructs the range from some ranges. */ join_range(R const &...ranges): p_ranges(ranges...) {} - join_range(R &&...ranges): p_ranges(std::forward(ranges)...) {} - join_range(join_range const &v): p_ranges(v.p_ranges) {} - join_range(join_range &&v): p_ranges(std::move(v.p_ranges)) {} - - join_range &operator=(join_range const &v) { - p_ranges = v.p_ranges; - return *this; - } - - join_range &operator=(join_range &&v) { - p_ranges = std::move(v.p_ranges); - return *this; - } + /** @brief The range is empty if all inner ranges are empty. */ bool empty() const { return std::apply([](auto const &...args) { return (... && args.empty()); }, p_ranges); } + /** @brief Pops out the front element from the first non-empty range. */ void pop_front() { detail::join_range_pop<0, sizeof...(R)>(p_ranges); } + /** @brief Accesses the front element of the first non-empty range. + * + * The behavior is undefined if the range is empty(). + */ reference front() const { return detail::join_range_front<0, sizeof...(R)>(p_ranges); } @@ -964,6 +1470,18 @@ namespace detail { using zip_value_t = typename detail::zip_value_type::type; } +/** @brief A wrapper range that zips multiple ranges together. + * + * This allows iteration of multiple ranges at once. The range will be + * considered empty once any of the ranges is empty (i.e. the shortest + * range is effectively the one that determines length). The resulting + * range is forward or input, depending on if any of the ranges is input. + * + * The value type is a pair (for two ranges) or a tuple (for more ranges) + * of the value types. The reference type is a pair or a tuple of the + * reference types. The size and difference types are common types between + * size and difference types of all contained ranges. + */ template struct zip_range: input_range> { using range_category = std::common_type_t< @@ -978,34 +1496,27 @@ private: std::tuple p_ranges; public: + /** @brief Not default constructible. */ zip_range() = delete; + + /** @brief Constructs the range from some ranges. */ zip_range(R const &...ranges): p_ranges(ranges...) {} - zip_range(R &&...ranges): p_ranges(std::forward(ranges)...) {} - zip_range(zip_range const &v): p_ranges(v.p_ranges) {} - zip_range(zip_range &&v): p_ranges(std::move(v.p_ranges)) {} - - zip_range &operator=(zip_range const &v) { - p_ranges = v.p_ranges; - return *this; - } - - zip_range &operator=(zip_range &&v) { - p_ranges = std::move(v.p_ranges); - return *this; - } + /** @brief The range is empty once any of the ranges is empty. */ bool empty() const { return std::apply([](auto const &...args) { return (... || args.empty()); }, p_ranges); } + /** @brief Pops out the front element from all ranges. */ void pop_front() { std::apply([](auto &...args) { (args.pop_front(), ...); }, p_ranges); } + /** @brief Accesses the front element of all ranges. */ reference front() const { return std::apply([](auto &&...args) { return reference{args.front()...}; @@ -1013,6 +1524,16 @@ public: } }; +/** @brief An appender range is an output range over a standard container. + * + * It will effectively `push_back(v)` into the container every time a `put(v)` + * is called on the appender. It works with any container that implements the + * `push_back(v)` method. + * + * It also implements some methods to manipulate the container itself which + * are not part of the output range interface. Not all of them might work, + * depending on the container stored. + */ template struct appender_range: output_range> { using value_type = typename T::value_type; @@ -1020,59 +1541,74 @@ struct appender_range: output_range> { using size_type = typename T::size_type; using difference_type = typename T::difference_type; + /** @brief Default constructs the container inside. */ appender_range(): p_data() {} + + /** @brief Constructs from a copy of a container. */ appender_range(T const &v): p_data(v) {} + + /** @brief Constructs an appender by moving a container into it. */ appender_range(T &&v): p_data(std::move(v)) {} - appender_range(appender_range const &v): p_data(v.p_data) {} - appender_range(appender_range &&v): p_data(std::move(v.p_data)) {} - - appender_range &operator=(appender_range const &v) { - p_data = v.p_data; - return *this; - } - - appender_range &operator=(appender_range &&v) { - p_data = std::move(v.p_data); - return *this; - } + /** @brief Assigns a copy of a container to the appender. */ appender_range &operator=(T const &v) { p_data = v; return *this; } + /** @brief Assigns a container to the appender by move. */ appender_range &operator=(T &&v) { p_data = std::move(v); return *this; } + /** @brief Clears the underlying container's contents. */ void clear() { p_data.clear(); } + + /** @brief Checks if the underlying container is empty. */ bool empty() const { return p_data.empty(); } + /** @brief Reserves some capacity in the container. */ void reserve(size_type cap) { p_data.reserve(cap); } + + /** @brief Resizes the container. */ void resize(size_type len) { p_data.resize(len); } + /** @brief Gets the container size. */ size_type size() const { return p_data.size(); } + + /** @brief Gets the container capacity. */ size_type capacity() const { return p_data.capacity(); } + /** @brief Appends a copy of a value to the end of the container. */ void put(typename T::const_reference v) { p_data.push_back(v); } + /** @brief Appends a value to the end of the container by move */ void put(typename T::value_type &&v) { p_data.push_back(std::move(v)); } + /** @brief Gets a reference to the underlying container. */ T &get() { return p_data; } private: T p_data; }; +/** @brief A convenience method to create an appender. */ template inline appender_range appender() { return appender_range(); } +/** @brief A convenience method to create an appender. */ +template +inline appender_range appender(T const &v) { + return appender_range(v); +} + +/** @brief A convenience method to create an appender. */ template inline appender_range appender(T &&v) { return appender_range(std::forward(v)); @@ -1106,9 +1642,22 @@ namespace detail { }; } +/** @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< @@ -1123,47 +1672,53 @@ struct iterator_range: input_range> { >; using difference_type = 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()]) {} - - iterator_range(iterator_range const &v): p_beg(v.p_beg), p_end(v.p_end) {} - iterator_range(iterator_range &&v): - p_beg(std::move(v.p_beg)), p_end(std::move(v.p_end)) + iterator_range(iterator_range const &v): + p_beg(&v[0]), p_end(&v[v.size()]) {} - iterator_range &operator=(iterator_range const &v) { - p_beg = v.p_beg; - p_end = v.p_end; - return *this; - } - - iterator_range &operator=(iterator_range &&v) { - p_beg = std::move(v.p_beg); - p_end = std::move(v.p_end); - return *this; - } - - /* satisfy input_range / forward_range */ + /** @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() { - ++p_beg; /* rely on iterators to do their own checks */ if constexpr(std::is_pointer_v) { - if (p_beg > p_end) { + 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; } - /* satisfy bidirectional_range */ + /** @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) { @@ -1174,25 +1729,61 @@ struct iterator_range: input_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); } - /* satisfy finite_random_access_range */ + /** @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 { return iterator_range(p_beg + start, p_beg + 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 { return p_beg[i]; } - /* statisfy contiguous_range */ - value_type *data() { return p_beg; } - value_type const *data() const { return p_beg; } + /** @brief Gets the pointer to the first element. + * + * Only valid/useful if the range is contiguous. + */ + value_type *data() { return &front(); } - /* satisfy output_range */ + /** @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) { @@ -1202,6 +1793,12 @@ struct iterator_range: input_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) { @@ -1215,43 +1812,65 @@ private: T p_beg, p_end; }; -template -iterator_range make_range(T beg, T end) { - return iterator_range{beg, 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()}; } }; -/* ranged_traits for initializer lists is not enough; we need to be able to - * call ostd::iter({initializer list}) and that won't match against a generic - * template, so we also need to define that here explicitly... +/** @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); @@ -1271,6 +1890,10 @@ namespace detail { }; } +/** @} */ + } /* namespace ostd */ #endif + +/** @} */