-
Notifications
You must be signed in to change notification settings - Fork 1
Feature: next_to_range & next_to_iter
#101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
1fa9056
implement iter_to_range
liss-h 60281a5
get rid of -Wchanges-meaning
liss-h bde58ff
fix
liss-h df38b36
resolve warning
liss-h 2066083
revert change to workflow
liss-h 92783e2
update README
liss-h cec8f07
also allow creating just an iterator
liss-h 7dbc41a
Merge branch 'develop' into feature/iter-wrapper
liss-h 8298090
rename
liss-h File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| #include <dice/template-library/next_to_range.hpp> | ||
|
|
||
| #include <cstddef> | ||
| #include <iostream> | ||
| #include <iterator> | ||
| #include <optional> | ||
| #include <ranges> | ||
|
|
||
| namespace dtl = dice::template_library; | ||
|
|
||
| struct iota_iter_impl { | ||
| using value_type = int; | ||
|
|
||
| private: | ||
| int cur_; | ||
|
|
||
| public: | ||
| explicit iota_iter_impl(int start = 0) noexcept : cur_{start} { | ||
| } | ||
|
|
||
| protected: | ||
| [[nodiscard]] std::optional<int> next() { | ||
| return cur_++; | ||
| } | ||
| }; | ||
|
|
||
| // Create just a C++-style iterator | ||
| using iota_iter = dtl::next_to_iter<iota_iter_impl>; | ||
| static_assert(std::input_iterator<iota_iter>); | ||
|
|
||
| // Create a C++-style range | ||
| using iota = dtl::next_to_range<iota_iter>; | ||
| static_assert(std::ranges::range<iota>); | ||
|
|
||
|
|
||
| int main() { | ||
| size_t num_iter = 0; | ||
| for (auto it = iota_iter{}; num_iter < 10; ++it) { | ||
| std::cout << *it << '\n'; | ||
| ++num_iter; | ||
| } | ||
|
|
||
| for (int const val : iota{5} | std::views::take(10)) { | ||
| std::cout << val << '\n'; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| #ifndef DICE_TEMPLATELIBRARY_NEXTTORANGE_HPP | ||
| #define DICE_TEMPLATELIBRARY_NEXTTORANGE_HPP | ||
|
|
||
| #include <cassert> | ||
| #include <cstddef> | ||
| #include <functional> | ||
| #include <iterator> | ||
| #include <optional> | ||
| #include <type_traits> | ||
| #include <utility> | ||
|
|
||
| namespace dice::template_library { | ||
|
|
||
| /** | ||
| * Wrapper to make a C++-style iterator out of a rust-style iterator. | ||
| * | ||
| * @tparam Iter base rust-style iterator | ||
| * | ||
| * Requirements for Iter (the following things must be at least protected): | ||
| * typename Iter::value_type; | ||
| * { Iter::next() } -> std::same_as<std::optional<typename Iter::value_type>>; | ||
| * | ||
| * We are not using a concept here, because concepts require things to be public | ||
| */ | ||
| template<typename Iter> | ||
|
bigerl marked this conversation as resolved.
|
||
| struct next_to_iter : Iter { | ||
| using base_iterator = Iter; | ||
| using sentinel = std::default_sentinel_t; | ||
| using value_type = typename Iter::value_type; | ||
| using reference = value_type const &; | ||
| using pointer = value_type const *; | ||
| using difference_type = std::ptrdiff_t; | ||
| using iterator_category = std::input_iterator_tag; | ||
|
|
||
| private: | ||
| std::optional<value_type> cur_; | ||
| std::optional<value_type> peeked_; | ||
|
|
||
| void advance() { | ||
| if (peeked_.has_value()) [[unlikely]] { | ||
| // fast path, we have already peaked the value | ||
| cur_ = std::exchange(peeked_, std::nullopt); | ||
| } else { | ||
| cur_ = this->next(); | ||
| } | ||
| } | ||
|
|
||
| public: | ||
| template<typename ...Args> | ||
| explicit next_to_iter(Args &&...args) : Iter{std::forward<Args>(args)...}, | ||
| cur_{this->next()} { | ||
| } | ||
|
|
||
| [[nodiscard]] reference operator*() const noexcept { | ||
| assert(cur_.has_value()); | ||
| return *cur_; | ||
| } | ||
|
|
||
| [[nodiscard]] pointer operator->() const noexcept { | ||
| assert(cur_.has_value()); | ||
| return &*cur_; | ||
| } | ||
|
|
||
| /** | ||
| * Access the current value of the iterator mutably. | ||
|
bigerl marked this conversation as resolved.
|
||
| * This is required because in ranges, iterators must have the same reference type for const and non-const access of operator*. | ||
| * This means a non-const overload of operator* that returns a non-const reference is not allowed. | ||
| * | ||
| * @return reference to current value | ||
| */ | ||
| [[nodiscard]] value_type &value() noexcept { | ||
| assert(cur_.has_value()); | ||
| return *cur_; | ||
| } | ||
|
|
||
| [[nodiscard]] value_type const &value() const noexcept { | ||
| assert(cur_.has_value()); | ||
| return *cur_; | ||
| } | ||
|
|
||
| next_to_iter &operator++() { | ||
| advance(); | ||
| return *this; | ||
| } | ||
|
|
||
| std::conditional_t<std::is_copy_constructible_v<base_iterator>, next_to_iter, void> operator++(int) { | ||
| if constexpr (std::is_copy_constructible_v<base_iterator>) { | ||
| auto cpy = *this; | ||
| advance(); | ||
| return cpy; | ||
| } else { | ||
| advance(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Takes a peek at the next element if there is one, but does not advance the iterator onto it. | ||
| * This function is meant to be used when the underlying iterator is not copyable or expensive to copy. | ||
| * | ||
| * @return nullopt if there is no next element, the element if there is a next element | ||
| */ | ||
| [[nodiscard]] std::optional<value_type> const &peek() { | ||
| if (!peeked_.has_value()) { | ||
| peeked_ = this->next(); | ||
| } | ||
|
|
||
| return peeked_; | ||
| } | ||
|
|
||
| friend bool operator==(next_to_iter const &self, sentinel) noexcept { | ||
| return !self.cur_.has_value(); | ||
| } | ||
|
|
||
| friend bool operator==(sentinel, next_to_iter const &self) noexcept { | ||
| return !self.cur_.has_value(); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * Make a C++-style range out of a rust-style iterator. | ||
| * This is meant to save you from writing all the boilerplate that is required for C++ ranges and iterators. | ||
| * | ||
| * @tparam Iter base rust-style iterator | ||
| * | ||
| * Requirements for Iter (the following things must be at least protected): | ||
| * typename Iter::value_type; | ||
| * { Iter::next() } -> std::same_as<std::optional<typename Iter::value_type>>; | ||
| * | ||
| * We are not using a concept here, because concepts require things to be public | ||
| */ | ||
| template<typename Iter> | ||
| struct next_to_range { | ||
| using iterator = next_to_iter<Iter>; | ||
| using sentinel = typename iterator::sentinel; | ||
| using value_type = typename iterator::value_type; | ||
|
|
||
| private: | ||
| std::conditional_t< | ||
| std::is_copy_constructible_v<Iter>, | ||
| Iter, | ||
| std::function<iterator()> | ||
| > make_iter_; | ||
|
|
||
| public: | ||
| template<typename ...Args> requires (!std::is_copy_constructible_v<Iter>) | ||
| explicit next_to_range(Args &&...args) | ||
| : make_iter_{[...args = std::forward<Args>(args)] { return iterator{args...}; }} { | ||
| } | ||
|
|
||
| template<typename ...Args> requires (std::is_copy_constructible_v<Iter>) | ||
| explicit next_to_range(Args &&...args) | ||
| : make_iter_{std::forward<Args>(args)...} { | ||
| } | ||
|
|
||
| /** | ||
| * @return a new iterator from the beginning of the range | ||
| * @note this *always* returns a new iterator, regardless if there are other iterators alive | ||
| */ | ||
| [[nodiscard]] iterator begin() const { | ||
| if constexpr (std::is_copy_constructible_v<Iter>) { | ||
| return iterator{make_iter_}; | ||
| } else { | ||
| return make_iter_(); | ||
| } | ||
| } | ||
|
|
||
| static constexpr sentinel end() noexcept { | ||
| return sentinel{}; | ||
| } | ||
| }; | ||
|
|
||
| } // namespace dice::template_library | ||
|
|
||
|
|
||
| #endif // DICE_TEMPLATELIBRARY_NEXTTORANGE_HPP | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN | ||
| #include <doctest/doctest.h> | ||
|
|
||
| #include <dice/template-library/next_to_range.hpp> | ||
|
|
||
|
|
||
| #include <algorithm> | ||
| #include <cstddef> | ||
| #include <initializer_list> | ||
| #include <optional> | ||
| #include <ranges> | ||
| #include <vector> | ||
|
|
||
| namespace dtl = dice::template_library; | ||
|
|
||
| TEST_SUITE("next_to_range") { | ||
| struct non_copy_iota_iter { | ||
| using value_type = int; | ||
|
|
||
| private: | ||
| int cur_; | ||
|
|
||
| public: | ||
| explicit non_copy_iota_iter(int start = 0) noexcept : cur_{start} { | ||
| } | ||
|
|
||
| non_copy_iota_iter(non_copy_iota_iter &&other) = default; | ||
| non_copy_iota_iter &operator=(non_copy_iota_iter &&other) = default; | ||
| non_copy_iota_iter(non_copy_iota_iter const &other) = delete; | ||
| non_copy_iota_iter &operator=(non_copy_iota_iter const &other) = delete; | ||
| ~non_copy_iota_iter() = default; | ||
|
|
||
| protected: | ||
| [[nodiscard]] std::optional<int> next() { | ||
| return cur_++; | ||
| } | ||
| }; | ||
|
|
||
| using non_copy_iota = dtl::next_to_range<non_copy_iota_iter>; | ||
| static_assert(std::ranges::range<non_copy_iota>); | ||
|
|
||
| struct values_yielder_iter { | ||
| using value_type = int; | ||
|
|
||
| private: | ||
| std::vector<int> values_; | ||
| size_t ix_ = 0; | ||
|
|
||
| public: | ||
| explicit values_yielder_iter(std::initializer_list<int> values) : values_{values} { | ||
| } | ||
|
|
||
| protected: | ||
| [[nodiscard]] std::optional<int> next() { | ||
| if (ix_ >= values_.size()) { | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| return values_[ix_++]; | ||
| } | ||
| }; | ||
|
|
||
| using values_yielder = dtl::next_to_range<values_yielder_iter>; | ||
| static_assert(std::ranges::range<values_yielder>); | ||
|
|
||
|
|
||
| TEST_CASE("sanity check") { | ||
| non_copy_iota ints{0}; | ||
| CHECK(std::ranges::equal(ints | std::views::take(3), std::vector<int>{0, 1, 2})); | ||
|
|
||
| auto even_ints = ints | ||
| | std::views::transform([](int x) { return x + 1; }) | ||
| | std::views::filter([](int x) { return x % 2 == 0; }) | ||
| | std::views::take(3); | ||
|
|
||
| CHECK(std::ranges::equal(even_ints, std::vector<int>{2, 4, 6})); | ||
| } | ||
|
|
||
| TEST_CASE("peeking") { | ||
| values_yielder ints{1, 2}; | ||
|
|
||
| auto iter = ints.begin(); | ||
| CHECK_NE(iter, ints.end()); | ||
| CHECK_EQ(*iter, 1); | ||
| CHECK_EQ(iter.peek(), 2); | ||
|
|
||
| ++iter; | ||
|
|
||
| CHECK_NE(iter, ints.end()); | ||
| CHECK_EQ(*iter, 2); | ||
| CHECK_EQ(iter.peek(), std::nullopt); | ||
|
|
||
| ++iter; | ||
| CHECK_EQ(iter, ints.end()); | ||
| CHECK_EQ(iter.peek(), std::nullopt); | ||
| } | ||
|
|
||
| TEST_CASE("postincrement") { | ||
| SUBCASE("non-copyable") { | ||
| non_copy_iota const ints{0}; | ||
| auto iter = ints.begin(); | ||
|
|
||
| static_assert(std::same_as<decltype(iter++), void>); | ||
|
|
||
| CHECK_EQ(*iter, 0); | ||
| iter++; | ||
| CHECK_EQ(*iter, 1); | ||
| } | ||
|
|
||
| SUBCASE("copyable") { | ||
| values_yielder const ints{0, 1}; | ||
| auto iter = ints.begin(); | ||
|
|
||
| static_assert(std::same_as<decltype(iter++), typename values_yielder::iterator>); | ||
|
|
||
| CHECK_EQ(*iter, 0); | ||
|
|
||
| auto cpy = iter++; | ||
| CHECK_EQ(*cpy, 0); | ||
| CHECK_EQ(*iter, 1); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.