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,273 @@ +//===----------------------------------------------------------------------===// +// +// 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 "test_proxy.h" +#include "test_iterators.h" + +#include + +struct MoveOnly { + int i; + + constexpr MoveOnly(int i) : i{i} {} + constexpr MoveOnly(MoveOnly&&) = default; + constexpr MoveOnly& operator=(MoveOnly&&) = default; + + constexpr MoveOnly(const MoveOnly&) = delete; + constexpr MoveOnly& operator=(const MoveOnly&) = delete; +}; + +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.i == 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.i == 8); + } + + // 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.i == 8); + } +} + +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); + std::same_as&> decltype(auto) iter2 = ++iter; + 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); + + std::same_as&> decltype(auto) iter2 = --iter; + 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}}; + + std::same_as&> decltype(auto) iter2 = iter += 2; + assert(&iter2 == &iter); + assert((*iter).data == 3); + + std::same_as&> decltype(auto) iter3 = iter -= 1; + 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.i == 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].i == 7); + assert(data[2].i == 5); + } + + return true; +} + +int main(int, const char**) { + test(); + static_assert(test()); + + return 0; +} \ No newline at end of file diff --git a/libcxx/test/support/test_proxy.h b/libcxx/test/support/test_proxy.h new file mode 100644 --- /dev/null +++ b/libcxx/test/support/test_proxy.h @@ -0,0 +1,309 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef SUPPORT_TEST_PROXY_H +#define SUPPORT_TEST_PROXY_H + +#include +#include +#include +#include + +#include "test_macros.h" + +#if TEST_STD_VER > 17 + +// Proxy +// ====================================================================== +// Proxy that can wrap a value of a reference. It simulates C++23's tuple +// but simplified to just hold one argument. +// Note that unlike tuple this class doesn't have special handling of swap +template +struct Proxy; + +template +inline constexpr bool IsProxy = false; + +template +inline constexpr bool IsProxy> = true; + +template +struct Proxy { + T data; + + template + requires std::same_as, + Proxy> friend constexpr auto + getData(P&& p) -> std::__copy_cvref_t { + return static_cast>(p.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()))>) + constexpr Proxy(Other&& other) : data{getData(std::forward(other))} {} + + template + requires(IsProxy> && std::assignable_from()))>) + constexpr Proxy& operator=(Other&& other) { + data = getData(std::forward(other)); + return *this; + } + + // const assignment required by indirectly_writable + template + requires(IsProxy> && std::assignable_from()))>) + constexpr const Proxy& operator=(Other&& other) const { + data = getData(std::forward(other)); + return *this; + } + + // no specialised swap function that takes const Proxy& and no specialised const member swap + // Calling swap(Proxy{}, Proxy{}) would fail (pass prvalues) +}; + +// 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 deference 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)} {} + + 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; + } + + 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_; + } +}; + +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_; + } +}; + +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 // TEST_STD_VER > 17 + +#endif // SUPPORT_TEST_PROXY_H