diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp @@ -104,6 +104,15 @@ test_iterators, Out>(); } +template +constexpr void test_proxy_in_iterators() { + test_iterators>, Out, sentinel_wrapper>>>(); + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); +} + constexpr bool test() { test_in_iterators>(); test_in_iterators>(); @@ -111,6 +120,12 @@ test_in_iterators>(); test_in_iterators>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + { // check that ranges::dangling is returned std::array out; std::same_as> auto ret = diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp @@ -107,11 +107,22 @@ test_iterators, Out>(); } +template +constexpr void test_proxy_in_iterators() { + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); +} + constexpr bool test() { test_in_iterators>(); test_in_iterators>(); test_in_iterators>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + { // check that ranges::dangling is returned std::array out; std::same_as> auto ret = diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_if.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_if.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_if.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_if.pass.cpp @@ -209,6 +209,30 @@ } } + { // test proxy iterator + { + std::array in = {4, 6, 87, 3, 88, 44, 45, 9}; + std::array out; + + ProxyRange proxyIn{in}; + ProxyRange proxyOut{out}; + + std::ranges::copy_if(proxyIn.begin(), proxyIn.end(), proxyOut.begin(), + [](auto const& i) { return i.data % 2 == 0; }); + assert((out == std::array{4, 6, 88, 44})); + } + { + std::array in = {4, 6, 87, 3, 88, 44, 45, 9}; + std::array out; + + ProxyRange proxyIn{in}; + ProxyRange proxyOut{out}; + + std::ranges::copy_if(proxyIn, proxyOut.begin(), [](const auto& i) { return i.data % 2 == 0; }); + assert((out == std::array{4, 6, 88, 44})); + } + } + return true; } diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp @@ -69,6 +69,15 @@ test_iterators, Out>(); } +template +constexpr void test_proxy_in_iterators() { + test_iterators>, Out, sentinel_wrapper>>>(); + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); +} + constexpr bool test() { test_in_iterators>(); test_in_iterators>(); @@ -76,6 +85,12 @@ test_in_iterators>(); test_in_iterators>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + { // check that every element is copied exactly once struct CopyOnce { bool copied = false; diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp @@ -95,6 +95,15 @@ test_iterators, Out>(); } +template +constexpr void test_proxy_in_iterators() { + test_iterators>, Out, sentinel_wrapper>>>(); + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); +} + struct IteratorWithMoveIter { using value_type = int; using difference_type = int; @@ -122,6 +131,12 @@ test_in_iterators>(); test_in_iterators>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + { // check that a move-only type works { MoveOnly a[] = {1, 2, 3}; @@ -140,6 +155,29 @@ assert(b[2].get() == 3); } } + + { // check that a move-only type works for ProxyIterator + { + MoveOnly a[] = {1, 2, 3}; + MoveOnly b[3]; + ProxyRange proxyA{a}; + ProxyRange proxyB{b}; + std::ranges::move(proxyA, std::begin(proxyB)); + assert(b[0].get() == 1); + assert(b[1].get() == 2); + assert(b[2].get() == 3); + } + { + MoveOnly a[] = {1, 2, 3}; + MoveOnly b[3]; + ProxyRange proxyA{a}; + ProxyRange proxyB{b}; + std::ranges::move(std::begin(proxyA), std::end(proxyA), std::begin(proxyB)); + assert(b[0].get() == 1); + assert(b[1].get() == 2); + assert(b[2].get() == 3); + } + } { // check that ranges::dangling is returned std::array out; diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.pass.cpp @@ -94,6 +94,14 @@ test_iterators, Out>(); } +template +constexpr void test_proxy_in_iterators() { + test_iterators>, Out, sentinel_wrapper>>>(); + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); +} + struct IteratorWithMoveIter { using value_type = int; using difference_type = int; @@ -119,6 +127,10 @@ test_in_iterators>(); test_in_iterators>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + { // check that a move-only type works { MoveOnly a[] = {1, 2, 3}; @@ -138,6 +150,29 @@ } } + { // check that a move-only type works for ProxyIterator + { + MoveOnly a[] = {1, 2, 3}; + MoveOnly b[3]; + ProxyRange proxyA{a}; + ProxyRange proxyB{b}; + std::ranges::move_backward(proxyA, std::ranges::next(proxyB.begin(), std::end(proxyB))); + assert(b[0].get() == 1); + assert(b[1].get() == 2); + assert(b[2].get() == 3); + } + { + MoveOnly a[] = {1, 2, 3}; + MoveOnly b[3]; + ProxyRange proxyA{a}; + ProxyRange proxyB{b}; + std::ranges::move_backward(std::begin(proxyA), std::end(proxyA), std::ranges::next(proxyB.begin(), std::end(proxyB))); + assert(b[0].get() == 1); + assert(b[1].get() == 2); + assert(b[2].get() == 3); + } + } + { // check that ranges::dangling is returned std::array out; std::same_as> auto ret = diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.reverse/ranges.reverse.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.reverse/ranges.reverse.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.reverse/ranges.reverse.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.reverse/ranges.reverse.pass.cpp @@ -24,6 +24,7 @@ #include #include "almost_satisfies_types.h" +#include "MoveOnly.h" #include "test_iterators.h" template > @@ -89,6 +90,10 @@ test_iterators, sentinel_wrapper>>(); test_iterators(); + test_iterators>>(); + test_iterators>>(); + test_iterators>>(); + // check that std::ranges::dangling is returned { [[maybe_unused]] std::same_as auto ret = std::ranges::reverse(std::array {1, 2, 3, 4}); @@ -109,6 +114,26 @@ } } + // Move only types work for ProxyIterator + { + { + MoveOnly a[] = {1, 2, 3}; + ProxyRange proxyA{a}; + std::ranges::reverse(proxyA.begin(), proxyA.end()); + assert(a[0].get() == 3); + assert(a[1].get() == 2); + assert(a[2].get() == 1); + } + { + MoveOnly a[] = {1, 2, 3}; + ProxyRange proxyA{a}; + std::ranges::reverse(proxyA); + assert(a[0].get() == 3); + assert(a[1].get() == 2); + assert(a[2].get() == 1); + } + } + return true; } diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.swap/ranges.swap_ranges.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.swap/ranges.swap_ranges.pass.cpp --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.swap/ranges.swap_ranges.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.swap/ranges.swap_ranges.pass.cpp @@ -162,6 +162,15 @@ } } +template +constexpr void test_proxy_in_iterators() { + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); + test_iterators>, Out>(); +} + constexpr bool test() { test_range(); @@ -195,6 +204,12 @@ test_iterators>(); test_iterators(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_proxy_in_iterators>>(); + test_sentinel(); test_different_lengths(); test_borrowed_input_range(); diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/sort/ranges.sort.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/sort/ranges.sort.pass.cpp --- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/sort/ranges.sort.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/sort/ranges.sort.pass.cpp @@ -205,6 +205,26 @@ [[maybe_unused]] std::same_as decltype(auto) result = std::ranges::sort(std::array{1, 2, 3}); } + // TODO: Enable the tests once the implementation switched to use iter_move/iter_swap + /* + { // ProxyIterator + { + std::array in = {2, 1, 3}; + ProxyRange proxy{in}; + + std::ranges::sort(proxy.begin(), proxy.end(), [](auto i, auto j) { return i.data < j.data; }); + assert((in == std::array{1, 2, 3})); + } + + { + std::array in = {2, 1, 3}; + ProxyRange proxy{in}; + std::ranges::sort(proxy, [](auto i, auto j) { return i.data < j.data; }); + assert((in == std::array{1, 2, 3})); + } + } + */ + return true; } diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/ranges.stable.sort.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/ranges.stable.sort.pass.cpp --- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/ranges.stable.sort.pass.cpp +++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/ranges.stable.sort.pass.cpp @@ -259,6 +259,26 @@ [[maybe_unused]] std::same_as decltype(auto) result = std::ranges::stable_sort(std::array{1, 2, 3}); } + + // TODO: Enable the tests once the implementation switched to use iter_move/iter_swap + /* + { // ProxyIterator + { + std::array in = {2, 1, 3}; + ProxyRange proxy{in}; + + std::ranges::stable_sort(proxy.begin(), proxy.end(), [](auto i, auto j) { return i.data < j.data; }); + assert((in == std::array{1, 2, 3})); + } + + { + std::array in = {2, 1, 3}; + ProxyRange proxy{in}; + std::ranges::stable_sort(proxy, [](auto i, auto j) { return i.data < j.data; }); + assert((in == std::array{1, 2, 3})); + } + } + */ } int main(int, char**) { diff --git a/libcxx/test/std/iterators/iterator.primitives/iterator.operations/advance.pass.cpp b/libcxx/test/std/iterators/iterator.primitives/iterator.operations/advance.pass.cpp --- a/libcxx/test/std/iterators/iterator.primitives/iterator.operations/advance.pass.cpp +++ b/libcxx/test/std/iterators/iterator.primitives/iterator.operations/advance.pass.cpp @@ -19,8 +19,15 @@ // template // constexpr void advance(Iter& i, Distance n); + +// TODO: test_iterators.h includes , and includes and . +// Lots of implementation headers under <__chrono/> and has signed to unsigned conversion, +// which will trigger the -Wsign-conversion warning. +// Once those headers are fixed, enable the -Wsign-conversion for this test by removing +// below + // Make sure we catch forced conversions to the difference_type if they happen. -// ADDITIONAL_COMPILE_FLAGS: -Wsign-conversion +// ADDITIONAL_COMPILE_FLAGS: -Wsign-conversion #include #include diff --git a/libcxx/test/support/test.support/test_proxy.pass.cpp b/libcxx/test/support/test.support/test_proxy.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/support/test.support/test_proxy.pass.cpp @@ -0,0 +1,279 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-incomplete-ranges + +#include "MoveOnly.h" +#include "test_iterators.h" + +#include + +constexpr void testProxy() { + // constructor value + { + Proxy p{5}; + assert(p.data == 5); + } + + // constructor reference + { + int i = 5; + Proxy p{i}; + assert(&p.data == &i); + } + + // constructor conversion + { + int i = 5; + Proxy p1{i}; + Proxy p2 = p1; + assert(p2.data == 5); + + Proxy p3{p2}; + assert(&(p3.data) == &(p2.data)); + + MoveOnly m1{8}; + Proxy p4 = std::move(m1); + + Proxy p5 = std::move(p4); + assert(p5.data.get() == 8); + } + + // assignment + { + Proxy p1{5}; + Proxy p2{6}; + p1 = p2; + assert(p1.data == 6); + + MoveOnly m1{8}; + Proxy p3 = std::move(m1); + Proxy p4{MoveOnly{9}}; + p4 = std::move(p3); + assert(p4.data.get() == 8); + + int i = 5, j = 6; + Proxy p5{i}; + p5 = Proxy{j}; + assert(p5.data == 6); + } + + // const assignment + { + int i = 5; + int j = 6; + const Proxy p1{i}; + const Proxy p2{j}; + p1 = p2; + assert(i == 6); + + MoveOnly m1{8}; + MoveOnly m2{9}; + Proxy p3 = std::move(m1); + const Proxy p4 = std::move(m2); + p4 = std::move(p3); + assert(p4.data.get() == 8); + } + + // compare + { + Proxy p1{5}; + Proxy p2{6}; + assert(p1 != p2); + assert(p1 < p2); + } +} + +static_assert(std::input_iterator>>); +static_assert(!std::forward_iterator>>); + +static_assert(std::forward_iterator>>); +static_assert(!std::bidirectional_iterator>>); + +static_assert(std::bidirectional_iterator>>); +static_assert(!std::random_access_iterator>>); + +static_assert(std::random_access_iterator>>); +static_assert(!std::contiguous_iterator>>); + +static_assert(std::random_access_iterator>>); +static_assert(!std::contiguous_iterator>>); + +template +constexpr void testInputIteratorOperation() { + int data[] = {1, 2}; + ProxyIterator iter{Iter{data}}; + sentinel_wrapper> sent{ProxyIterator{Iter{data + 2}}}; + + std::same_as> decltype(auto) result = *iter; + assert(result.data == 1); + auto& iter2 = ++iter; + static_assert(std::is_same_v&>); + assert(&iter2 == &iter); + assert((*iter).data == 2); + ++iter; + assert(iter == sent); +} + +template +constexpr void testForwardIteratorOperation() { + int data[] = {1, 2}; + ProxyIterator iter{Iter{data}}; + + std::same_as> decltype(auto) it2 = iter++; + assert((*it2).data == 1); + assert((*iter).data == 2); +} + +template +constexpr void testBidirectionalIteratorOperation() { + int data[] = {1, 2}; + ProxyIterator iter{Iter{data}}; + ++iter; + assert((*iter).data == 2); + + auto& iter2 = --iter; + static_assert(std::is_same_v&>); + assert(&iter2 == &iter); + assert((*iter).data == 1); + ++iter; + + std::same_as> decltype(auto) iter3 = iter--; + assert((*iter).data == 1); + assert((*iter3).data == 2); +} + +template +constexpr void testRandomAccessIteratorOperation() { + int data[] = {1, 2, 3, 4, 5}; + ProxyIterator iter{Iter{data}}; + + auto& iter2 = iter += 2; + static_assert(std::is_same_v&>); + assert(&iter2 == &iter); + assert((*iter).data == 3); + + auto& iter3 = iter -= 1; + static_assert(std::is_same_v&>); + assert(&iter3 == &iter); + assert((*iter).data == 2); + + std::same_as> decltype(auto) r = iter[2]; + assert(r.data == 4); + + std::same_as> decltype(auto) iter4 = iter - 1; + assert((*iter4).data == 1); + + std::same_as> decltype(auto) iter5 = iter4 + 2; + assert((*iter5).data == 3); + + std::same_as> decltype(auto) iter6 = 3 + iter4; + assert((*iter6).data == 4); + + std::same_as> decltype(auto) n = iter6 - iter5; + assert(n == 1); + + assert(iter4 < iter5); + assert(iter3 <= iter5); + assert(iter5 > iter4); + assert(iter6 >= iter4); +} + +constexpr void testProxyIterator() { + // input iterator operations + { + testInputIteratorOperation>(); + testInputIteratorOperation>(); + testInputIteratorOperation>(); + testInputIteratorOperation>(); + testInputIteratorOperation>(); + } + + // forward iterator operations + { + testForwardIteratorOperation>(); + testForwardIteratorOperation>(); + testForwardIteratorOperation>(); + testForwardIteratorOperation>(); + } + + // bidirectional iterator operations + { + testBidirectionalIteratorOperation>(); + testBidirectionalIteratorOperation>(); + testBidirectionalIteratorOperation>(); + } + + // random access iterator operations + { + testRandomAccessIteratorOperation>(); + testRandomAccessIteratorOperation>(); + } +} + +constexpr void testProxyRange() { + int data[] = {3, 4, 5}; + ProxyRange r{data}; + std::same_as> decltype(auto) it = std::ranges::begin(r); + assert((*it).data == 3); + it += 3; + assert(it == std::ranges::end(r)); +} + +template +concept StdMoveWorks = requires(std::iter_value_t val, Iter iter) { val = std::move(*iter); }; + +static_assert(StdMoveWorks); +static_assert(!StdMoveWorks>); + +// although this "works" but it actually creates a copy instead of move +static_assert(StdMoveWorks>); + +using std::swap; + +template +concept SwapWorks = requires(Iter iter1, Iter iter2) { swap(*iter1, *iter2); }; + +static_assert(SwapWorks); +static_assert(!SwapWorks>); + +constexpr bool test() { + testProxy(); + testProxyIterator(); + testProxyRange(); + + // iter_move + { + MoveOnly data[] = {5, 6, 7}; + ProxyRange r{data}; + auto it = r.begin(); + std::iter_value_t moved = std::ranges::iter_move(it); + assert(moved.data.get() == 5); + } + + // iter_swap + { + MoveOnly data[] = {5, 6, 7}; + ProxyRange r{data}; + auto it1 = r.begin(); + auto it2 = it1 + 2; + std::ranges::iter_swap(it1, it2); + assert(data[0].get() == 7); + assert(data[2].get() == 5); + } + + return true; +} + +int main(int, const char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/support/test_iterators.h b/libcxx/test/support/test_iterators.h --- a/libcxx/test/support/test_iterators.h +++ b/libcxx/test/support/test_iterators.h @@ -12,11 +12,14 @@ #include #include #include +#include #include +#include #include #include "test_macros.h" + // This iterator meets C++20's Cpp17OutputIterator requirements, as described // in Table 90 ([output.iterators]). template @@ -820,6 +823,300 @@ } // namespace adl +// Proxy +// ====================================================================== +// Proxy that can wrap a value or a reference. It simulates C++23's tuple +// but simplified to just hold one argument. +// Note that unlike tuple, this class deliberately doesn't have special handling +// of swap to cause a compilation error if it's used in an algorithm that relies +// on plain swap instead of ranges::iter_swap. +// This class is useful for testing that if algorithms support proxy iterator +// properly, i.e. calling ranges::iter_swap and ranges::iter_move instead of +// plain swap and std::move +template +struct Proxy; + +template +inline constexpr bool IsProxy = false; + +template +inline constexpr bool IsProxy> = true; + +template +struct Proxy { + T data; + + constexpr T& getData() & { return data; } + + constexpr const T& getData() const& { return data; } + + constexpr T&& getData() && { return static_cast(data); } + + constexpr const T&& getData() const&& { return static_cast(data); } + + template + requires std::constructible_from + constexpr Proxy(U&& u) : data{std::forward(u)} {} + + // This constructor covers conversion from cvref of Proxy, including non-const/const versions of copy/move constructor + template + requires(IsProxy> && std::constructible_from().getData())>) + constexpr Proxy(Other&& other) : data{std::forward(other).getData()} {} + + template + requires(IsProxy> && std::assignable_from().getData())>) + constexpr Proxy& operator=(Other&& other) { + data = std::forward(other).getData(); + return *this; + } + + // const assignment required to make ProxyIterator model std::indirectly_writable + template + requires(IsProxy> && std::assignable_from().getData())>) + constexpr const Proxy& operator=(Other&& other) const { + data = std::forward(other).getData(); + return *this; + } + + // no specialised swap function that takes const Proxy& and no specialised const member swap + // Calling swap(Proxy{}, Proxy{}) would fail (pass prvalues) + + // Compare operators are defined for the convenience of the tests + friend constexpr bool operator==(const Proxy&, const Proxy&) + requires std::equality_comparable + = default; + + friend constexpr auto operator<=>(const Proxy&, const Proxy&) + requires std::three_way_comparable + = default; +}; + +// This is to make ProxyIterator model `std::indirectly_readable` +template class TQual, template class UQual> + requires requires { typename std::common_reference_t, UQual>; } +struct std::basic_common_reference, Proxy, TQual, UQual> { + using type = Proxy, UQual>>; +}; + +template + requires requires { typename std::common_type_t; } +struct std::common_type, Proxy> { + using type = Proxy>; +}; + +// ProxyIterator +// ====================================================================== +// It wraps `Base` iterator and when dereferenced it returns a Proxy +// It simulates C++23's zip_view::iterator but simplified to just wrap +// one base iterator. +// Note it forwards value_type, iter_move, iter_swap. e.g if the base +// iterator is int*, +// operator* -> Proxy +// iter_value_t -> Proxy +// iter_move -> Proxy +template +struct ProxyIteratorBase {}; + +template + requires std::derived_from< + typename std::iterator_traits::iterator_category, + std::input_iterator_tag> +struct ProxyIteratorBase { + using iterator_category = std::input_iterator_tag; +}; + +template +consteval auto get_iterator_concept() { + if constexpr (std::random_access_iterator) { + return std::random_access_iterator_tag{}; + } else if constexpr (std::bidirectional_iterator) { + return std::bidirectional_iterator_tag{}; + } else if constexpr (std::forward_iterator) { + return std::forward_iterator_tag{}; + } else { + return std::input_iterator_tag{}; + } +} + +template +struct ProxyIterator : ProxyIteratorBase { + Base base_; + + using iterator_concept = decltype(get_iterator_concept()); + using value_type = Proxy>; + using difference_type = std::iter_difference_t; + + ProxyIterator() + requires std::default_initializable + = default; + + constexpr ProxyIterator(Base base) : base_{std::move(base)} {} + + template + requires std::constructible_from + constexpr ProxyIterator(T&& t) : base_{std::forward(t)} {} + + friend constexpr decltype(auto) base(const ProxyIterator& p) { return base(p.base_); } + + // Specialization of iter_move + // If operator* returns Proxy, iter_move will return Proxy + // Note std::move(*it) returns Proxy&&, which is not what we want as + // it will likely result in a copy rather than a move + friend constexpr Proxy> iter_move(const ProxyIterator& p) noexcept { + return {std::ranges::iter_move(p.base_)}; + } + + // Specialization of iter_swap + // Note std::swap(*x, *y) would fail to compile as operator* returns prvalues + // and std::swap takes non-const lvalue references + friend constexpr void iter_swap(const ProxyIterator& x, const ProxyIterator& y) noexcept { + std::ranges::iter_swap(x.base_, y.base_); + } + + // to satisfy input_iterator + constexpr Proxy> operator*() const { return {*base_}; } + + constexpr ProxyIterator& operator++() { + ++base_; + return *this; + } + + constexpr void operator++(int) { ++*this; } + + friend constexpr bool operator==(const ProxyIterator& x, const ProxyIterator& y) + requires std::equality_comparable { + return x.base_ == y.base_; + } + + // to satisfy forward_iterator + constexpr ProxyIterator operator++(int) + requires std::forward_iterator { + auto tmp = *this; + ++*this; + return tmp; + } + + // to satisfy bidirectional_iterator + constexpr ProxyIterator& operator--() + requires std::bidirectional_iterator { + --base_; + return *this; + } + + constexpr ProxyIterator operator--(int) + requires std::bidirectional_iterator { + auto tmp = *this; + --*this; + return tmp; + } + + // to satisfy random_access_iterator + constexpr ProxyIterator& operator+=(difference_type n) + requires std::random_access_iterator { + base_ += n; + return *this; + } + + constexpr ProxyIterator& operator-=(difference_type n) + requires std::random_access_iterator { + base_ -= n; + return *this; + } + + constexpr Proxy> operator[](difference_type n) const + requires std::random_access_iterator { + return {base_[n]}; + } + + friend constexpr bool operator<(const ProxyIterator& x, const ProxyIterator& y) + requires std::random_access_iterator { + return x.base_ < y.base_; + } + + friend constexpr bool operator>(const ProxyIterator& x, const ProxyIterator& y) + requires std::random_access_iterator { + return x.base_ > y.base_; + } + + friend constexpr bool operator<=(const ProxyIterator& x, const ProxyIterator& y) + requires std::random_access_iterator { + return x.base_ <= y.base_; + } + + friend constexpr bool operator>=(const ProxyIterator& x, const ProxyIterator& y) + requires std::random_access_iterator { + return x.base_ >= y.base_; + } + + friend constexpr auto operator<=>(const ProxyIterator& x, const ProxyIterator& y) + requires(std::random_access_iterator && std::three_way_comparable) { + return x.base_ <=> y.base_; + } + + friend constexpr ProxyIterator operator+(const ProxyIterator& x, difference_type n) + requires std::random_access_iterator { + return ProxyIterator{x.base_ + n}; + } + + friend constexpr ProxyIterator operator+(difference_type n, const ProxyIterator& x) + requires std::random_access_iterator { + return ProxyIterator{n + x.base_}; + } + + friend constexpr ProxyIterator operator-(const ProxyIterator& x, difference_type n) + requires std::random_access_iterator { + return ProxyIterator{x.base_ - n}; + } + + friend constexpr difference_type operator-(const ProxyIterator& x, const ProxyIterator& y) + requires std::random_access_iterator { + return x.base_ - y.base_; + } +}; + +static_assert(std::indirectly_readable>); +static_assert(std::indirectly_writable, Proxy>); + +template +struct ProxySentinel { + BaseSent base_; + + ProxySentinel() = default; + constexpr ProxySentinel(BaseSent base) : base_{std::move(base)} {} + + template + requires std::equality_comparable_with + friend constexpr bool operator==(const ProxyIterator& p, const ProxySentinel& sent) { + return p.base_ == sent.base_; + } +}; + +#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES) +template + requires std::ranges::view +struct ProxyRange { + Base base_; + + constexpr auto begin() { return ProxyIterator{std::ranges::begin(base_)}; } + + constexpr auto end() { return ProxySentinel{std::ranges::end(base_)}; } + + constexpr auto begin() const + requires std::ranges::input_range { + return ProxyIterator{std::ranges::begin(base_)}; + } + + constexpr auto end() const + requires std::ranges::input_range { + return ProxySentinel{std::ranges::end(base_)}; + } +}; + +template + requires std::ranges::viewable_range +ProxyRange(R&&) -> ProxyRange>; +#endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES) + #endif // TEST_STD_VER > 17 #endif // SUPPORT_TEST_ITERATORS_H