diff --git a/experimental/tiledb/common/dag/utility/range_join.h b/experimental/tiledb/common/dag/utility/range_join.h index 89f39f36ec5..db9a660f2e8 100644 --- a/experimental/tiledb/common/dag/utility/range_join.h +++ b/experimental/tiledb/common/dag/utility/range_join.h @@ -73,7 +73,7 @@ #include #include #include -#include "arrow_proxy.hpp" +#include "tiledb/common/arrow_proxy.hpp" #include "external/include/span/span.hpp" diff --git a/experimental/tiledb/common/dag/utility/test/compile_utils_main.cc b/experimental/tiledb/common/dag/utility/test/compile_utils_main.cc index c9f29280f62..b158e690c65 100644 --- a/experimental/tiledb/common/dag/utility/test/compile_utils_main.cc +++ b/experimental/tiledb/common/dag/utility/test/compile_utils_main.cc @@ -26,12 +26,12 @@ * THE SOFTWARE. */ -#include "../arrow_proxy.hpp" #include "../bounded_buffer.h" #include "../print_types.h" #include "../range_join.h" #include "../spinlock.h" #include "../traits.h" +#include "tiledb/common/arrow_proxy.hpp" int main() { } diff --git a/experimental/tiledb/common/dag/utility/arrow_proxy.hpp b/tiledb/common/arrow_proxy.hpp similarity index 94% rename from experimental/tiledb/common/dag/utility/arrow_proxy.hpp rename to tiledb/common/arrow_proxy.hpp index 92203459ca1..5a8cc0c23e1 100644 --- a/experimental/tiledb/common/dag/utility/arrow_proxy.hpp +++ b/tiledb/common/arrow_proxy.hpp @@ -5,7 +5,7 @@ * * The MIT License * - * @copyright Copyright (c) 2022 TileDB, Inc. + * @copyright Copyright (c) 2022-2024 TileDB, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -52,4 +52,7 @@ struct arrow_proxy { } }; +template +arrow_proxy(T&&) -> arrow_proxy; + #endif // TILEDB_ARROW_PROXY_HPP diff --git a/tiledb/common/iterator_facade.h b/tiledb/common/iterator_facade.h new file mode 100644 index 00000000000..be97d52e441 --- /dev/null +++ b/tiledb/common/iterator_facade.h @@ -0,0 +1,495 @@ +/** + * @file iterator_facade.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file implements an `iterator_facade` that will be used as part of TileDB + * external sort. The implementation is a modified version of + * `iterator_facade` from the `vectorofbool/neo-fun` github repository, + * (https://github.com/vector-of-bool/neo-fun/commit/b6c38c8 Mar 21, 2024). It + * is used here under the terms of the Boost Software License 1.0 and is + * Copyright (c) the author(s). + */ + +#ifndef TILEDB_ITERATOR_FACADE_H +#define TILEDB_ITERATOR_FACADE_H + +#include +#include +#include + +#include "arrow_proxy.hpp" + +namespace detail { + +// clang-format off +template +concept sized_sentinel_of = + requires(const Iter& it, const Sentinel& sentinel) { + it.distance_to(sentinel); + }; + +template +struct infer_difference_type { + using type = std::ptrdiff_t; +}; + +template + requires sized_sentinel_of +struct infer_difference_type { + static const T& _it; + using type = decltype(_it.distance_to(_it)); +}; + +template +using infer_difference_type_t = typename infer_difference_type::type; + +template +struct infer_value_type { + static const T& _it; + using type = std::remove_cvref_t; +}; + +template + requires requires { typename T::value_type; } +struct infer_value_type { + using type = typename T::value_type; +}; + +template +using infer_value_type_t = typename infer_value_type::type; + +template +concept can_increment = + requires(T& t) { + t.increment(); + }; + +template +concept can_decrement = + requires(T& t) { + t.decrement(); + }; + +template +concept can_advance = + requires(T& t, const infer_difference_type_t d) { + t.advance(d); + }; + +template +concept can_to_address = + requires (const T& t) { + { t.to_address() } -> std::contiguous_iterator; + }; + +template +concept iter_is_random_access = + sized_sentinel_of && + can_advance; + +template +concept iter_is_contiguous = iter_is_random_access and can_to_address; + +template +concept iter_is_bidirectional = + iter_is_random_access || + can_decrement; + +template +concept iter_is_single_pass = requires { + requires bool(T::single_pass_iterator); +}; + +template +concept iter_is_forward = !iter_is_single_pass && requires(const T& item) { + { item == item }; +}; + +// clang-format on + +// This causes internalASTError in cppcheck 2.13.0_1 (Homebrew) on macOS 14.3.1 +template +concept noexcept_incrementable = requires(T& iter) { + { iter.increment() } noexcept; +} || requires(T& item) { + { item += 1 } noexcept; +}; + +template +concept iter_diff = std::convertible_to>; + +struct iterator_facade_base; + +template +concept iter_facade_type = + std::is_base_of_v>; + +template +concept iter_self = iter_facade_type; + +template +concept random_access_iter_self = iter_self && iter_is_random_access; + +template +concept bidirectional_iter_self = iter_self && iter_is_bidirectional; + +struct iterator_facade_base { + /** + * If this is a random_access_iterator, returns the distance from the right + * to the left, i.e. how many times to apply ++right to reach `left`. + */ + template Sent> + [[nodiscard]] constexpr friend auto operator-( + const Sent& sent, const Self& self) noexcept { + return self.distance_to(sent); + } + + template Diff> + [[nodiscard]] constexpr friend Self operator-( + const Self& self, Diff off) noexcept { + using diff_type = infer_difference_type_t; + using signed_diff_type = std::make_signed_t; + return self + -static_cast(off); + } + + template Diff> + [[nodiscard]] constexpr friend Self operator+( + const Self& self, Diff off) noexcept { + auto cp = self; + return cp += off; + } + + template Diff> + [[nodiscard]] constexpr friend Self operator+( + Diff off, const Self& self) noexcept { + return self + off; + } + + template Diff> + constexpr friend Self& operator+=(Self& self, Diff off) noexcept { + self.advance(static_cast>(off)); + return self; + } + + template Diff> + constexpr friend Self& operator-=(Self& self, Diff off) noexcept { + return self = self - off; + } + + /** + * Advance the iterator one position forward. Implemented as a call to + * `.increment()`, if present, otherwise `*this += 1` + * + * @todo If `increment()` is not present, but advance() is, we should use + * advance(1) + */ + template + constexpr friend Self& operator++(Self& s) noexcept( + noexcept_incrementable) { + if constexpr (can_increment) { + // If there is an increment(), assume it is the most efficient way to + // advance, even if we have an advance() + s.increment(); + } else if constexpr (iter_is_random_access) { + // Just offset by one + s += 1; + } else { + static_assert( + iter_is_random_access || can_increment, + "Iterator subclass must provide an `increment` or `advance(n)` " + "method"); + } + return s; + } + + // clang-format off + template + constexpr friend + std::conditional_t< + detail::iter_is_single_pass, + void, + std::remove_cvref_t> + operator++(Self& self, int) noexcept(noexcept_incrementable) { + // clang-format on + if constexpr (detail::iter_is_single_pass) { + // The iterator is a single-pass iterator. It isn't safe to make and + // return an old copy. + ++self; + } else { + auto cp = self; + ++self; + return cp; + } + } + + template + constexpr friend Self& operator--(Self& self) noexcept { + if constexpr (can_decrement) { + self.decrement(); + } else { + self -= 1; + } + return self; + } + + template + constexpr friend std::remove_cvref_t operator--( + Self& self, int) noexcept { + auto cp = self; + --self; + return cp; + } + + /** + * With three-way comparison, we can get away with much simpler + * comparison/equality operators, since we can also rely on synthesized + * rewrites + */ + template S> + [[nodiscard]] constexpr friend std::strong_ordering operator<=>( + const Self& self, const S& right) noexcept { + auto dist = self - right; + auto rel = dist <=> 0; + return rel; + } + + /** + * With three-way comparison, we can get away with much simpler + * comparison/equality operators, since we can also rely on synthesized + * rewrites + */ + template S> + [[nodiscard]] constexpr friend bool operator<( + const Self& self, const S& right) noexcept { + auto dist = self - right; + auto rel = dist < 0; + return rel; + } +}; + +} // namespace detail + +/** + * An iterator_facade fills-out the interface of an iterator based on just a few + * methods be present on the derived class (provided as the CRTP parameter). + * + * The following methods MUST be provided: + * + * - Derived::dereference() - Return value of the operator*(). Need not be an + * actual reference. + * - Derived::increment() OR Derived::advance(ptrdiff_t) - (Or both) Used to + * implement operator++() and operator++(int). If both methods are provided, + * `increment()` will be prefered for single increment operations. + * + * With these two methods defined, the iterator is a forward_iterator. If + * `advance(ptrdiff_t)` is provided, the iterator is also bidirectional. + * + * + * ====== Iterator Equality + * + * The generated iterator type is equality-comparible with any object of type + * S if the derived class implements `distance_to(S)`. This includes sentinel + * types and other instances of the iterator. + * + * + * ====== Single-pass Input + * + * If the class provides a static member `single_pass_iterator` that is `true`, + * then the iterator will be an input_iterator, and the operator++(int) + * (postfix decrement) will return `void`. + * + * + * ====== Bidirectional + * + * If the following are provided, the iterator is bidirectional: + * + * - Derived::decrement() OR Derived::advance(ptrdiff_t) - Used to implement + * operator--() and operator--(int). + * + * Note that unless the requirements for Random Access are met, `advance()` + * will only be called with `1` or `-1`. + * + * + * ====== Random Access + * + * If the following are provided, the iterator is random_access_iterator: + * + * - Derived::advance(ptrdiff_t p) - Move the iterator by `p` (may be negative!) + * - Derived::distance_to(Derived other) - Return the "distance" to `other`, + * that is: The number of types *this must be incremented to be equal to + * `other`, or the numner of times *this must be decremented to reach `other` + * (which should yield a negative number). + * + * These two methods are used to implement the remainder of the iterator + * functionality. + * + * + * NOTE: A specialization of std::iterator_traits is provided that + * provides the required iterator typedefs. You cannot access e.g. + * `iterator_facade<...>::value_type` directly, and must instead go through + * `iterator_traits::value_type`. + */ +template +class iterator_facade : public detail::iterator_facade_base { + public: + using self_type = Derived; + + private: + constexpr self_type& _self() noexcept { + return static_cast(*this); + } + constexpr const self_type& _self() const noexcept { + return static_cast(*this); + } + + public: + /** + * Implement operator* in terms of `.dereference()` + */ + [[nodiscard]] constexpr decltype(auto) operator*() const + noexcept(noexcept(_self().dereference())) { + return _self().dereference(); + } + + /** + * Implement arrow in terms of `operator*`, but: + * + * If the return type of operator* is a non-reference type, returns an + * arrow_proxy that wraps the returned value. + * + * If the return type is a reference type, returns a pointer to the returned + * object. + */ + constexpr decltype(auto) operator->() const + noexcept(noexcept(_self().dereference())) { + if constexpr (detail::can_to_address) { + return _self().to_address(); + } else if constexpr (std::is_reference_v< + std::iter_reference_t>) { + // If operator*() returns a reference, just return that address + return std::addressof(**this); + } else { + // It returned a value, so we need to wrap it in an arrow_proxy for the + // caller + return arrow_proxy{**this}; + } + } + + template D> + [[nodiscard]] constexpr decltype(auto) operator[](D pos) const noexcept { + return *(_self() + pos); + } +}; + +template +class iterator_wrapper_facade : public iterator_facade { + using base = typename iterator_wrapper_facade::iterator_facade; + using self_type = Derived; + + protected: + InnerIterator wrapped_iterator; + + public: + iterator_wrapper_facade() = default; + explicit iterator_wrapper_facade(InnerIterator it) + : wrapped_iterator(it) { + } + + template D> + constexpr void advance(D off) noexcept { + std::advance(wrapped_iterator, off); + } + + // clang-format off + constexpr auto distance_to(const self_type& other) const noexcept + requires std::derived_from< + std::random_access_iterator_tag, + typename std::iterator_traits::iterator_category + > { + return other.wrapped_iterator - wrapped_iterator; + } + // clang-format on + + constexpr friend bool operator==( + const self_type& left, const self_type& other) noexcept { + return left.wrapped_iterator == other.wrapped_iterator; + } +}; + +// Putting things in std:: namespace is a bad idea, but it is necessary for +// `iterator_traits` to work correctly with `iterator_facade`. +namespace std { + +template + requires std::is_base_of_v +struct iterator_traits { + static const Derived& _const_it; + using value_type = detail::infer_value_type_t; + using reference = decltype(*_const_it); + using pointer = decltype(_const_it.operator->()); + using difference_type = detail::infer_difference_type_t; + + // Pick the iterator category based on the interfaces that it provides + // @todo Add support for output iterators and for contiguous iterators + using iterator_category = std::conditional_t< + // Contiguous? + detail::iter_is_contiguous, + std::contiguous_iterator_tag, + // Not + std::conditional_t< + // Random access? + detail::iter_is_random_access, + std::random_access_iterator_tag, + // Nope + std::conditional_t< + // Bidirectional? + detail::iter_is_bidirectional, + std::bidirectional_iterator_tag, + // Noh + std::conditional_t< + // Is it single-pass? + detail::iter_is_forward, + // Otherwise it is a forward iterator + std::forward_iterator_tag, + // Than means it is an input iterator + std::input_iterator_tag>>>>; + + using iterator_concept = iterator_category; +}; + +template + requires detail::can_to_address and + std::derived_from> +struct pointer_traits { + using pointer = decltype(std::addressof(*std::declval())); + using element_type = std::remove_pointer_t; + using difference_type = std::ptrdiff_t; +}; + +} // namespace std +#endif diff --git a/tiledb/common/test/CMakeLists.txt b/tiledb/common/test/CMakeLists.txt index 552212e48a3..24935f815c5 100644 --- a/tiledb/common/test/CMakeLists.txt +++ b/tiledb/common/test/CMakeLists.txt @@ -41,3 +41,8 @@ commence(unit_test memory_tracker_types) this_target_object_libraries(baseline) conclude(unit_test) +commence(unit_test iterator_facade) + this_target_sources(main.cc unit_iterator_facade.cc) + this_target_object_libraries(baseline) +conclude(unit_test) + diff --git a/tiledb/common/test/unit_iterator_facade.cc b/tiledb/common/test/unit_iterator_facade.cc new file mode 100644 index 00000000000..8eaaf72ca3f --- /dev/null +++ b/tiledb/common/test/unit_iterator_facade.cc @@ -0,0 +1,1026 @@ + +/** + * @file unit_iterator_facade.cc + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2024 TileDB, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @section DESCRIPTION + * + * This file implements unit tests for `iterator_facade` that will be used as + * part of TileDB external sort. The tests include those from + * `iterator_facade.test.cpp` from the `vectorofbool/neo-fun` github repository, + * (https://github.com/vector-of-bool/neo-fun/commit/b6c38c8 Mar 21, 2024). It + * is used here under the terms of the Boost Software License 1.0 and is + * Copyright (c) the author(s). + * + */ + +#include +#include +#include +#include "../iterator_facade.h" + +TEST_CASE("iterator_facade: Null test", "[iterator_facade][null_test]") { + REQUIRE(true); +} + +struct null_iterator : public iterator_facade {}; + +TEST_CASE("iterator_facade: null_iterator", "[iterator_facade]") { + null_iterator it; + (void)it; +} + +struct minimal_iterator : public iterator_facade { + int value = 0; + + int dereference() const { + return value; + } + int distance_to(minimal_iterator o) const { + return *o - value; + } + void advance(int off) { + value += off; + } + + // This should be inferred, but it seems to be necessary to specify it here. + // @todo Implement inference for this in iterator_facade. + bool operator==(minimal_iterator o) const noexcept { + return *o == **this; + } +}; + +TEST_CASE("iterator_facade: minimal_iterator", "[iterator_facade]") { + CHECK(std::input_iterator); + CHECK(!std::output_iterator); + CHECK(std::forward_iterator); + CHECK(std::bidirectional_iterator); + CHECK(std::random_access_iterator); +} + +/* + * The following are from vector-of-bool neo-fun/iterator_facade.test.cc + */ +namespace { + +template +class as_string_iterator + : public iterator_wrapper_facade, Iter> { + using as_string_iterator::iterator_wrapper_facade::iterator_wrapper_facade; + + public: + std::string dereference() const noexcept { + return std::to_string(*this->wrapped_iterator); + } +}; + +template +as_string_iterator(It) -> as_string_iterator; + +class iota_iterator : public iterator_facade { + int _value = 0; + + public: + iota_iterator() = default; + explicit iota_iterator(int i) + : _value(i) { + } + + // Since this is a fake iterator, and is maintaining its "value" itself, + // we can't return a reference to the value. + int dereference() const noexcept { + return _value; + } + + // Trying to do this breaks iterator_traits for iterator_facade. + // Since this is just a contrived situation, it's not worth trying to fix. + // Note that it will work -- we can set values, but the iterator_traits + // will not be correct -- and wil cause compiler error if we try to use. + // int& dereference() noexcept { return _value; } + + void advance(int off) noexcept { + _value += off; + } + int distance_to(iota_iterator o) const noexcept { + return *o - **this; + } + bool operator==(iota_iterator o) const noexcept { + return *o == **this; + } +}; + +} // namespace + +TEST_CASE("Create an iota_iterator") { + iota_iterator it; + iota_iterator stop{44}; + + CHECK(std::input_iterator); + CHECK(!std::output_iterator); + CHECK(std::forward_iterator); + CHECK(std::bidirectional_iterator); + CHECK(std::random_access_iterator); + + CHECK((stop - it) == 44); + CHECK(*(stop - 4) == 40); + CHECK(it < stop); + CHECK(it <= stop); + CHECK_FALSE(it > stop); + CHECK_FALSE(it >= stop); + CHECK(std::distance(it, stop) == 44); + + CHECK(it[33] == 33); + CHECK(it[-9] == -9); + CHECK(stop[2] == 46); + CHECK(stop[-44] == 0); + + CHECK((stop - it) == 44); + CHECK((it - stop) == -44); + + CHECK(it != stop); + CHECK((it + 44) == stop); + CHECK(it == (stop - 44)); +} + +namespace { + +class mutable_iota_iterator : public iterator_facade { + mutable int _value{0}; + + public: + mutable_iota_iterator() = default; + explicit mutable_iota_iterator(int i) + : _value(i) { + } + + // This is the same kind of contrived iterator as before, but here the + // iterator is referring to an external value, which it can return by + // reference, even if the iterator is const. + int& dereference() const noexcept { + return _value; + } + + void advance(int off) noexcept { + _value += off; + } + int distance_to(mutable_iota_iterator o) const noexcept { + return *o - **this; + } + bool operator==(mutable_iota_iterator o) const noexcept { + return *o == **this; + } +}; +} // namespace + +TEST_CASE("mutable_iota_iterator") { + CHECK(std::input_iterator); + CHECK(std::output_iterator); + CHECK(std::forward_iterator); + CHECK(std::bidirectional_iterator); + CHECK(std::random_access_iterator); + + mutable_iota_iterator it; + CHECK(*it == 0); + *it = 42; + CHECK(*it == 42); +} + +TEST_CASE("arrow_proxy") { + arrow_proxy s{""}; + s->append("Hello, "); + s->append("world!"); + CHECK(*s.operator->() == "Hello, world!"); +} + +TEST_CASE("Trivial iterator") { + struct deref_iter : iterator_facade { + int* value = nullptr; + auto& dereference() /*const*/ noexcept { + // cppcheck-suppress nullPointer + return *value; + } + auto dereference() const noexcept { + return *value; + } + + deref_iter(int& i) + : value(&i) { + } + }; + + // Just some very simple tests. The trivial iterator does not even have + // increment or decrement. + + int i = 12; + deref_iter it{i}; + + CHECK(*it == 12); + i = 7; + CHECK(*it == 7); +} + +TEST_CASE("Single-pass iterator") { + struct in_iter : iterator_facade { + int value = 0; + enum { single_pass_iterator = true }; + + const int& dereference() const noexcept { + return value; + } + void increment() noexcept { + ++value; + } + }; + + in_iter it; + CHECK(*it == 0); + static_assert(std::is_same_v); + ++it; + CHECK(*it == 1); + static_assert(std::is_void_v); +} + +TEST_CASE("Transforming iterator") { + std::vector values = {1, 2, 3, 4}; + as_string_iterator it{values.begin()}; + + CHECK(*it == "1"); + ++it; + CHECK(*it == "2"); + CHECK_FALSE(it == as_string_iterator(values.begin())); + // Post-increment returns a copy of the iterator + auto copy = it++; + CHECK(*copy == "2"); + + static_assert( + std::is_same_v()), arrow_proxy>); + // Even though we are acting on a temporary, the append() will return a new + // string + auto thirty_four = it->append("4"); + CHECK(thirty_four == "34"); + + copy = copy - 1; + CHECK(*copy == "1"); + CHECK(*(copy + 3) == "4"); + CHECK(*(3 + copy) == "4"); + + ++copy; + auto copy2 = copy--; + CHECK(*copy == "1"); + CHECK(*copy2 == "2"); + + // Advance by a negative number created from an unsigned + CHECK(*copy == "1"); + ++copy; + copy -= 1u; + CHECK(*copy == "1"); +} + +TEST_CASE("Sentinel support") { + struct until_7_iter : iterator_facade { + int value = 0; + struct sentinel_type {}; + + auto dereference() const noexcept { + return value; + } + auto increment() noexcept { + ++value; + } + + auto distance_to(sentinel_type) const noexcept { + return 7 - value; + } + bool operator==(sentinel_type s) const noexcept { + return distance_to(s) == 0; + } + }; + + struct seven_range { + auto begin() { + return until_7_iter(); + } + auto end() { + return until_7_iter::sentinel_type(); + } + }; + + int sum = 0; + for (auto i : seven_range()) { + sum += i; + CHECK(i < 7); + } + CHECK(sum == (1 + 2 + 3 + 4 + 5 + 6)); + + auto it = seven_range().begin(); + auto stop = seven_range().end(); + CHECK(it != stop); + CHECK(stop != it); + CHECK_FALSE(it == stop); + CHECK_FALSE(stop == it); + +#if !_MSC_VER + /// XXX: Last checked, MSVC has an issue finding the correct operator-() via + /// ADL. If you're using MSVC and reading this comment in the future, revive + /// this snippet and try again. + CHECK((until_7_iter::sentinel_type() - it) == 7); +#endif +} + +namespace { +enum class month : int { + january, + february, + march, + april, + may, + june, + july, + august, + september, + october, + november, + december, +}; + +/* + * Example of a simple iterator that iterates over the months of the year, + * copied from + * `https://vector-of-bool.github.io/2020/06/13/cpp20-iter-facade.html` + */ +class month_iterator : public iterator_facade { + month _cur = month::january; + + public: + month_iterator() = default; + explicit month_iterator(month m) + : _cur(m) { + } + + auto begin() const { + return *this; + } + auto end() const { + return month_iterator(month(int(month::december) + 1)); + } + + void increment() { + _cur = month(int(_cur) + 1); + } + void decrement() { + _cur = month(int(_cur) - 1); + } + void advance(int off) { + _cur = month(int(_cur) + off); + } + + const month& dereference() const { + return _cur; + } + + int distance_to(month_iterator other) const { + return int(other._cur) - int(_cur); + } + // This shouldn't be necessary, but seems to be required to determine + // the iterator category. + bool operator==(month_iterator other) const { + return _cur == other._cur; + } +}; + +/* + * A second month_iterator to test writing to the iterator. + */ + +class month_iterator_2 : public iterator_facade { + // Since we are faking an iterator over data, we can make the value mutable. + mutable month _cur = month::january; + + public: + month_iterator_2() = default; + explicit month_iterator_2(month m) + : _cur(m) { + } + + month& dereference() const { + return _cur; + } + + void advance(int n) { + _cur = month(int(_cur) + n); + } + int distance_to(month_iterator_2 o) const { + return int(o._cur) - int(_cur); + } + + bool operator==(month_iterator_2 o) const { + return _cur == o._cur; + } +}; + +} // namespace + +TEMPLATE_TEST_CASE( + "iterator_facade: month_iterator", + "[iterator_facade]", + month_iterator, + month_iterator_2) { + CHECK(std::input_iterator); + if (std::is_same_v) { + CHECK(std::output_iterator); + } else { + CHECK(!std::output_iterator); + } + + CHECK(std::forward_iterator); + CHECK(std::bidirectional_iterator); + CHECK(std::random_access_iterator); + + TestType it; + CHECK(*it == month::january); + ++it; + CHECK(*it == month::february); + ++it; + CHECK(*it == month::march); + ++it; + CHECK(*it == month::april); + ++it; + CHECK(*it == month::may); + ++it; + CHECK(*it == month::june); + ++it; + CHECK(*it == month::july); + ++it; + CHECK(*it == month::august); + ++it; + CHECK(*it == month::september); + ++it; + CHECK(*it == month::october); + ++it; + CHECK(*it == month::november); + ++it; + CHECK(*it == month::december); + // We should be able to increment once more and get the sentinel value, + // but this is not working for some reason. + // @todo Fix this. + // ++it; + // CHECK(it == month_iterator::sentinel_type()); +} + +TEST_CASE("iterator_facade: month_iterator_2 write") { + month_iterator_2 it; + *it = month::august; + CHECK(*it == month::august); + ++it; + CHECK(*it == month::september); + + *it = month::may; + CHECK(*it == month::may); + it++; + CHECK(*it == month::june); + CHECK(*it++ == month::june); + CHECK(*it == month::july); + + it.advance(3); + CHECK(*it == month::october); + *it = month::july; + it += 3; + CHECK(*it == month::october); + + auto a = *(it + 0); + CHECK(a == month::october); + auto b = *(it + 1); + CHECK(b == month::november); + + // These fail for some reason. + // @todo Fix this. + // auto c = it[0]; + // CHECK(c == month::october); + // auto d = it[1]; + // CHECK(d == month::november); + + CHECK(*(it + 0) == month::october); + CHECK(*(it + 1) == month::november); + + // These fail for some reason. + // @todo Fix this. + // CHECK(it[0] == month::october); + // CHECK(it[1] == month::november); +} + +/* + * Test arrow and arrow_proxy + */ +struct bar { + int val = -1; + bar() + : val(199) { + } + explicit bar(int x) + : val(x) { + } + int set(int x) { + auto tmp = val; + val = x; + return tmp; + } + int get() { + return val; + } +}; + +struct foo { + std::vector values{ + bar{0}, + bar{0}, + bar{0}, + bar{0}, + bar{0}, + bar{0}, + bar{0}, + bar{0}, + bar{0}, + bar{0}}; + + class iterator : public iterator_facade { + size_t index_ = 0; + std::vector::iterator values_iter; + + public: + iterator() = delete; + iterator(std::vector::iterator values_iter, size_t index = 0) + : index_(index) + , values_iter(values_iter) { + } + + auto& dereference() const { + return values_iter[index_]; + } + // auto &dereference() { return values_iter[index_]; } + + bool equal_to(iterator o) const { + return index_ == o.index_; + } + void advance(int n) { + index_ += n; + } + int distance_to(iterator o) const { + return o.index_ - index_; + } + }; + + auto begin() { + return iterator{values.begin()}; + } + auto end() { + return iterator{values.begin(), size(values)}; + } +}; + +TEST_CASE("iterator_facade: arrow") { + std::vector x(10); + foo f; + auto it = f.begin(); + CHECK((*it).get() == 0); + CHECK(it[0].get() == 0); + CHECK(it->get() == 0); + + CHECK((*(it + 1)).get() == 0); + CHECK(it[1].get() == 0); + CHECK((*(it + 1)).set(42) == 0); + CHECK((*(it + 1)).get() == 42); + CHECK(it[1].get() == 42); + ++it; + CHECK((*(it + 0)).get() == 42); + CHECK(it[0].get() == 42); + --it; + CHECK((*(it + 1)).get() == 42); + CHECK(it[1].get() == 42); + + CHECK(it[0].get() == 0); + CHECK(it->set(43) == 0); + CHECK(it->get() == 43); + CHECK(it->set(41) == 43); + CHECK(it->get() == 41); + CHECK(it[0].get() == 41); + CHECK(it[1].get() == 42); + + CHECK(f.values[0].get() == 41); + CHECK(f.values[1].get() == 42); + CHECK(f.values[2].get() == 0); +} + +namespace { + +/** + * This structure wraps an std::vector and provides a simple iterator over it. + * The iterator that is wrapped is always the plain iterator, not a + * const_iterator; we define a const_iterator also using iterator_facade. Note + * that if the simple_mutable_struct is const, the internally stored vector will + * also be const, and so the begin() used below will return a const_iterator to + * the vector. We can either conditionally set the wrapped iterator type to be + * const or non-const, or we can mark the internal vector as mutable. We could + * also make the simple_mutable_iterator prameterized on iterator type. Below we + * use the second option. + */ +template +struct simple_mutable_struct { + static_assert(!std::is_const_v); + + // One option: Wrap just iterator -- need to make this mutable so that begin() + // returns just an iterator even if the simple_mutable_struct is const + /* mutable */ std::vector value; + + simple_mutable_struct() { + value.resize(10); + } + + template + class simple_mutable_iterator + : public iterator_facade> { + // Second option, wrap the iterator using the appropriate const or non-const + using Iter = std::conditional_t< + std::is_const_v, + typename std::vector>::const_iterator, + typename std::vector>::iterator>; + Iter current_; + + public: + simple_mutable_iterator() = default; + explicit simple_mutable_iterator(Iter i) + : current_(i) { + } + + template + requires(!std::is_const_v) + explicit simple_mutable_iterator(simple_mutable_iterator i) + : current_(i.current_) { + } + + V& dereference() const noexcept { + return *current_; + } + + void advance(int off) noexcept { + current_ += off; + } + + auto distance_to(simple_mutable_iterator o) const noexcept { + return o.current_ - current_; + } + + bool operator==(simple_mutable_iterator o) const noexcept { + return o.current_ == current_; + } + }; + + using iterator = simple_mutable_iterator; + using const_iterator = simple_mutable_iterator; + + using value_type = std::iterator_traits::value_type; + using reference = std::iterator_traits::reference; + using const_reference = std::iterator_traits::reference; + + auto begin() { + return iterator{value.begin()}; + } + auto end() { + return iterator{value.end()}; + } + auto begin() const { + return const_iterator{value.begin()}; + } + auto end() const { + return const_iterator{value.end()}; + } + auto cbegin() const { + return const_iterator{value.begin()}; + } + auto cend() const { + return const_iterator{value.end()}; + } + auto size() const { + return value.size(); + } +}; + +template +class pointer_wrapper : public iterator_facade> { + Value* current_ = nullptr; + + public: + pointer_wrapper() = default; + explicit pointer_wrapper(Value* i) + : current_(i) { + } + + Value& dereference() const noexcept { + return *current_; + } + + constexpr void advance(int off) noexcept { + current_ += off; + } + + constexpr auto distance_to(pointer_wrapper o) const noexcept { + return o.current_ - current_; + } + + // This is supposed to be inferred.... Either from equal_to or from + // distance_to -- but it seems to be necessary to specify it here. + constexpr bool operator==(pointer_wrapper o) const noexcept { + return o.current_ == current_; + } +}; +} // namespace + +template +void iterator_test(Iterator begin, [[maybe_unused]] Iterator end) { + Iterator it = begin; + ++it; + CHECK(*it == 14); + it++; + CHECK(*it == 15); + it += 3; + CHECK(*it == 18); + CHECK(*it++ == 18); + CHECK(*it == 19); + CHECK(*it-- == 19); + CHECK(*it == 18); + CHECK(*++it == 19); + CHECK(*--it == 18); + it -= 2; + CHECK(*it == 16); + it--; + CHECK(*it == 15); + --it; + CHECK(*it == 14); + + CHECK(it[0] == 14); + + it = begin; + for (int i = 0; i < 10; ++i) { + CHECK(it[i] == 13 + i); + } + + it = begin; + CHECK(it == it); + CHECK(it <= it); + CHECK(it >= it); + + auto it2 = it; + CHECK(it == it2); + CHECK(it2 == it); + CHECK(it2 <= it); + CHECK(it2 >= it); + ++it; + CHECK(it != it2); + CHECK(it2 != it); + CHECK(it2 < it); + CHECK(it > it2); + CHECK(!(it <= it2)); + CHECK(!(it2 >= it)); +} + +template +void struct_test(S& s) { + auto it = s.begin(); + + std::iota(s.begin(), s.end(), 13); + CHECK(std::equal(s.begin(), s.end(), s.value.begin())); + CHECK(std::equal(s.cbegin(), s.cend(), s.value.cbegin())); + CHECK(std::equal(s.begin(), s.end(), s.cbegin())); + CHECK(std::equal(s.cbegin(), s.cend(), s.begin())); + CHECK(std::equal( + s.begin(), + s.end(), + std::vector{13, 14, 15, 16, 17, 18, 19, 20, 21, 22}.begin())); + + iterator_test(s.begin(), s.end()); + iterator_test(s.cbegin(), s.cend()); + + int counter = 0; + for (auto i : s) { + CHECK(i == 13 + counter); + ++counter; + } + it = s.begin(); + *it = 17; + CHECK(*it == 17); + + std::iota(s.begin(), s.end(), 13); + [[maybe_unused]] auto cit = s.cbegin(); + counter = 0; + for (auto c = s.cbegin(); c != s.cend(); ++c) { + CHECK(*c == 13 + counter); + ++counter; + } + + // Error: trying to assign to const_iterator + // *cit = 0; +}; + +template +void const_struct_test(const S& s) { + [[maybe_unused]] auto it = s.begin(); + + CHECK(std::equal(s.begin(), s.end(), s.value.begin())); + CHECK(std::equal(s.cbegin(), s.cend(), s.value.cbegin())); + CHECK(std::equal(s.begin(), s.end(), s.cbegin())); + CHECK(std::equal(s.cbegin(), s.cend(), s.begin())); + CHECK(std::equal( + s.begin(), + s.end(), + std::vector{13, 14, 15, 16, 17, 18, 19, 20, 21, 22}.begin())); + + iterator_test(s.begin(), s.end()); + iterator_test(s.cbegin(), s.cend()); +}; + +TEST_CASE("simple_mutable_struct", "[iterator_facade]") { + simple_mutable_struct s; + + struct_test(s); + const_struct_test(s); + + std::iota(s.begin(), s.end(), 13); + + iterator_test(s.begin(), s.end()); + iterator_test(s.cbegin(), s.cend()); +} + +template +void range_test() { + CHECK(std::ranges::range); + CHECK(std::ranges::input_range); + + if (is_output) { + CHECK(std::ranges::output_range); + } else { + CHECK(!std::ranges::output_range); + } + + CHECK(std::ranges::forward_range); + CHECK(std::ranges::bidirectional_range); + CHECK(std::ranges::random_access_range); + + if (std::same_as< + std::remove_cvref_t, + std::vector>) { + CHECK(std::ranges::contiguous_range); + } else { + CHECK(!std::ranges::contiguous_range); + } + + CHECK(std::ranges::sized_range); +} + +TEST_CASE("simple_mutable_struct range concepts", "[iterator_facade]") { + range_test>(); + range_test>(); + range_test const, false>(); + + range_test, false>(); + range_test, false>(); + range_test, false>(); +} + +template +void iterator_test() { + CHECK(std::input_iterator); + + if (is_output) { + CHECK( + std::output_iterator::value_type>); + } else { + CHECK( + !std::output_iterator::value_type>); + } + + CHECK(std::forward_iterator); + CHECK(std::bidirectional_iterator); + CHECK(std::random_access_iterator); + if (std::is_same_v< + std::remove_cvref_t, + typename std::vector< + typename std::iterator_traits::value_type>::iterator> || + std::is_pointer_v) { + CHECK(std::contiguous_iterator); + } else { + CHECK(!std::contiguous_iterator); + } +} + +TEST_CASE("simple_mutable_struct iterator concepts", "[iterator_facade]") { + iterator_test::iterator>(); + iterator_test::iterator>(); + iterator_test::const_iterator, false>(); + + iterator_test>(); + iterator_test, false>(); + iterator_test, false>(); + + iterator_test(); + iterator_test(); + iterator_test(); + + // These will all fail (evidently) since the container is const. + // The range tests pass, however, even though the range concepts are + // defined in terms of the iterator concepts.... + // @todo Fix so that iterator and range checks are consistent. +#if 0 + iterator_test::iterator, false>(); + iterator_test::iterator, false>(); + iterator_test::const_iterator, false>(); + iterator_test>(); + iterator_test, false>(); + iterator_test, false>(); +#endif +} + +template < + class I, + class Value, + class Category = std::random_access_iterator_tag, + class Reference = Value&, + class ConstReference = const Reference, + class RValueReference = Value&&, + class Difference = std::ptrdiff_t> +void iterator_types_test() { + CHECK(std::is_same_v, std::remove_cvref_t>); + CHECK(std::is_same_v, Reference>); + // This is a C++23 concept -- enable once we move to C++23 + // CHECK(std::is_same_v, Reference>); + CHECK(std::is_same_v, Difference>); + CHECK(std::is_same_v, RValueReference>); + CHECK(std::is_same_v< + typename std::iterator_traits::iterator_category, + Category>); +} + +TEST_CASE("simple_mutable_struct iterator types", "[iterator_facade]") { + iterator_types_test::iterator, int>(); + iterator_types_test::const_iterator, const int>(); + iterator_types_test::iterator, int>(); + iterator_types_test::const_iterator, const int>(); +} + +TEST_CASE("pointer wrapper iterator types", "[iterator_facade]") { + iterator_types_test, int>(); + iterator_types_test(); + + iterator_types_test, const int>(); + iterator_types_test(); + iterator_types_test, int const>(); + iterator_types_test(); + + iterator_types_test::iterator, int>(); + iterator_types_test::const_iterator, const int>(); + // @todo: Why are these only forward iterators? + iterator_types_test< + const simple_mutable_struct::iterator, + int, + std::forward_iterator_tag>(); + iterator_types_test< + const simple_mutable_struct::const_iterator, + const int, + std::forward_iterator_tag>(); + + // @todo: Why are thest only input iterators? + iterator_types_test< + const pointer_wrapper, + int, + std::forward_iterator_tag>(); + iterator_types_test< + const pointer_wrapper, + const int, + std::forward_iterator_tag>(); + + iterator_types_test< + const pointer_wrapper, + int const, + std::forward_iterator_tag>(); +}