[libcxx-commits] [libcxx] a81cc1f - [libcxx][ranges] Create a test tool `ProxyIterator` that customises `iter_move` and `iter_swap`
Hui Xie via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Jul 7 16:15:32 PDT 2022
Author: Hui Xie
Date: 2022-07-08T00:00:21+01:00
New Revision: a81cc1fc071247dea4367d077a1faf2dca15ccc1
URL: https://github.com/llvm/llvm-project/commit/a81cc1fc071247dea4367d077a1faf2dca15ccc1
DIFF: https://github.com/llvm/llvm-project/commit/a81cc1fc071247dea4367d077a1faf2dca15ccc1.diff
LOG: [libcxx][ranges] Create a test tool `ProxyIterator` that customises `iter_move` and `iter_swap`
It is meant to be used in ranges algorithm tests.
It is much simplified version of C++23's tuple + zip_view.
Using std::swap would cause compilation failure and using `std::move` would not create the correct rvalue proxy which would result in copies.
Differential Revision: https://reviews.llvm.org/D129099
Added:
libcxx/test/support/test.support/test_proxy.pass.cpp
Modified:
libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp
libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp
libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_if.pass.cpp
libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_n.pass.cpp
libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move.pass.cpp
libcxx/test/std/algorithms/alg.modifying.operations/alg.move/ranges.move_backward.pass.cpp
libcxx/test/std/algorithms/alg.modifying.operations/alg.reverse/ranges.reverse.pass.cpp
libcxx/test/std/algorithms/alg.modifying.operations/alg.swap/ranges.swap_ranges.pass.cpp
libcxx/test/std/algorithms/alg.sorting/alg.sort/sort/ranges.sort.pass.cpp
libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/ranges.stable.sort.pass.cpp
libcxx/test/std/iterators/iterator.primitives/iterator.operations/advance.pass.cpp
libcxx/test/support/test_iterators.h
Removed:
################################################################################
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
index e692dca54de47..390bafed7aa54 100644
--- 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 @@ constexpr void test_in_iterators() {
test_iterators<contiguous_iterator<int*>, Out>();
}
+template <class Out>
+constexpr void test_proxy_in_iterators() {
+ test_iterators<ProxyIterator<cpp20_input_iterator<int*>>, Out, sentinel_wrapper<ProxyIterator<cpp20_input_iterator<int*>>>>();
+ test_iterators<ProxyIterator<forward_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<bidirectional_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<random_access_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<contiguous_iterator<int*>>, Out>();
+}
+
constexpr bool test() {
test_in_iterators<cpp20_input_iterator<int*>>();
test_in_iterators<forward_iterator<int*>>();
@@ -111,6 +120,12 @@ constexpr bool test() {
test_in_iterators<random_access_iterator<int*>>();
test_in_iterators<contiguous_iterator<int*>>();
+ test_proxy_in_iterators<ProxyIterator<cpp20_input_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<forward_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<bidirectional_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<random_access_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<contiguous_iterator<int*>>>();
+
{ // check that ranges::dangling is returned
std::array<int, 4> out;
std::same_as<std::ranges::in_out_result<std::ranges::dangling, int*>> 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
index d5e67d5fdb691..0d3ccf6ab2ffc 100644
--- 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 @@ constexpr void test_in_iterators() {
test_iterators<contiguous_iterator<int*>, Out>();
}
+template <class Out>
+constexpr void test_proxy_in_iterators() {
+ test_iterators<ProxyIterator<bidirectional_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<random_access_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<contiguous_iterator<int*>>, Out>();
+}
+
constexpr bool test() {
test_in_iterators<bidirectional_iterator<int*>>();
test_in_iterators<random_access_iterator<int*>>();
test_in_iterators<contiguous_iterator<int*>>();
+ test_proxy_in_iterators<ProxyIterator<bidirectional_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<random_access_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<contiguous_iterator<int*>>>();
+
{ // check that ranges::dangling is returned
std::array<int, 4> out;
std::same_as<std::ranges::in_out_result<std::ranges::dangling, int*>> 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
index 07a366a29d652..0fcfa7855551b 100644
--- 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 @@ constexpr bool test() {
}
}
+ { // test proxy iterator
+ {
+ std::array in = {4, 6, 87, 3, 88, 44, 45, 9};
+ std::array<int, 4> 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<int, 4> 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
index e5c2efef9c883..1af5f984aa29b 100644
--- 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 @@ constexpr void test_in_iterators() {
test_iterators<contiguous_iterator<int*>, Out>();
}
+template <class Out>
+constexpr void test_proxy_in_iterators() {
+ test_iterators<ProxyIterator<cpp20_input_iterator<int*>>, Out, sentinel_wrapper<ProxyIterator<cpp20_input_iterator<int*>>>>();
+ test_iterators<ProxyIterator<forward_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<bidirectional_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<random_access_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<contiguous_iterator<int*>>, Out>();
+}
+
constexpr bool test() {
test_in_iterators<cpp20_input_iterator<int*>>();
test_in_iterators<forward_iterator<int*>>();
@@ -76,6 +85,12 @@ constexpr bool test() {
test_in_iterators<random_access_iterator<int*>>();
test_in_iterators<contiguous_iterator<int*>>();
+ test_proxy_in_iterators<ProxyIterator<cpp20_input_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<forward_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<bidirectional_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<random_access_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<contiguous_iterator<int*>>>();
+
{ // 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
index 07dca417399f5..ba94e745ea27d 100644
--- 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 @@ constexpr void test_in_iterators() {
test_iterators<contiguous_iterator<int*>, Out>();
}
+template <class Out>
+constexpr void test_proxy_in_iterators() {
+ test_iterators<ProxyIterator<cpp20_input_iterator<int*>>, Out, sentinel_wrapper<ProxyIterator<cpp20_input_iterator<int*>>>>();
+ test_iterators<ProxyIterator<forward_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<bidirectional_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<random_access_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<contiguous_iterator<int*>>, Out>();
+}
+
struct IteratorWithMoveIter {
using value_type = int;
using
diff erence_type = int;
@@ -122,6 +131,12 @@ constexpr bool test() {
test_in_iterators<random_access_iterator<int*>>();
test_in_iterators<contiguous_iterator<int*>>();
+ test_proxy_in_iterators<ProxyIterator<cpp20_input_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<forward_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<bidirectional_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<random_access_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<contiguous_iterator<int*>>>();
+
{ // check that a move-only type works
{
MoveOnly a[] = {1, 2, 3};
@@ -140,6 +155,29 @@ constexpr bool test() {
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<int, 4> 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
index 2ddafe78dcba9..2211409f6ab3c 100644
--- 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 @@ constexpr void test_in_iterators() {
test_iterators<contiguous_iterator<int*>, Out>();
}
+template <class Out>
+constexpr void test_proxy_in_iterators() {
+ test_iterators<ProxyIterator<bidirectional_iterator<int*>>, Out, sentinel_wrapper<ProxyIterator<bidirectional_iterator<int*>>>>();
+ test_iterators<ProxyIterator<bidirectional_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<random_access_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<contiguous_iterator<int*>>, Out>();
+}
+
struct IteratorWithMoveIter {
using value_type = int;
using
diff erence_type = int;
@@ -119,6 +127,10 @@ constexpr bool test() {
test_in_iterators<random_access_iterator<int*>>();
test_in_iterators<contiguous_iterator<int*>>();
+ test_proxy_in_iterators<ProxyIterator<bidirectional_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<random_access_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<contiguous_iterator<int*>>>();
+
{ // check that a move-only type works
{
MoveOnly a[] = {1, 2, 3};
@@ -138,6 +150,29 @@ constexpr bool test() {
}
}
+ { // 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<int, 4> out;
std::same_as<std::ranges::in_out_result<std::ranges::dangling, int*>> 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
index 14474706a15e7..843719d96e8e3 100644
--- 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 <ranges>
#include "almost_satisfies_types.h"
+#include "MoveOnly.h"
#include "test_iterators.h"
template <class Iter, class Sent = sentinel_wrapper<Iter>>
@@ -89,6 +90,10 @@ constexpr bool test() {
test_iterators<contiguous_iterator<int*>, sentinel_wrapper<contiguous_iterator<int*>>>();
test_iterators<int*>();
+ test_iterators<ProxyIterator<bidirectional_iterator<int*>>>();
+ test_iterators<ProxyIterator<random_access_iterator<int*>>>();
+ test_iterators<ProxyIterator<contiguous_iterator<int*>>>();
+
// check that std::ranges::dangling is returned
{
[[maybe_unused]] std::same_as<std::ranges::dangling> auto ret = std::ranges::reverse(std::array {1, 2, 3, 4});
@@ -109,6 +114,26 @@ constexpr bool test() {
}
}
+ // 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
index 8b8f4f0007576..d5ffd54c18690 100644
--- 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 @@ constexpr void test_rval_range() {
}
}
+template <class Out>
+constexpr void test_proxy_in_iterators() {
+ test_iterators<ProxyIterator<cpp20_input_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<forward_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<bidirectional_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<random_access_iterator<int*>>, Out>();
+ test_iterators<ProxyIterator<contiguous_iterator<int*>>, Out>();
+}
+
constexpr bool test() {
test_range();
@@ -195,6 +204,12 @@ constexpr bool test() {
test_iterators<int*, random_access_iterator<int*>>();
test_iterators<int*, int*>();
+ test_proxy_in_iterators<ProxyIterator<cpp20_input_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<forward_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<bidirectional_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<random_access_iterator<int*>>>();
+ test_proxy_in_iterators<ProxyIterator<contiguous_iterator<int*>>>();
+
test_sentinel();
test_
diff erent_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
index 6c2eb075fc1b9..020a8de85898b 100644
--- 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 @@ constexpr bool test() {
[[maybe_unused]] std::same_as<std::ranges::dangling> 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
index 553cf67b7ab46..3f94a72396db5 100644
--- 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 @@ void test() {
[[maybe_unused]] std::same_as<std::ranges::dangling> 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
index 8c89472e9f03e..8cf86407d7ad1 100644
--- 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 <RandomAccessIterator Iter, class Distance>
// constexpr void advance(Iter& i, Distance n);
+
+// TODO: test_iterators.h includes <ranges>, and <ranges> includes <chrono> and <atomic>.
+// Lots of implementation headers under <__chrono/> and <atomic> 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
+// <TODO:Remove brackets> below
+
// Make sure we catch forced conversions to the
diff erence_type if they happen.
-// ADDITIONAL_COMPILE_FLAGS: -Wsign-conversion
+// ADDITIONAL_COMPILE_FLAGS<TODO:Remove brackets>: -Wsign-conversion
#include <iterator>
#include <cassert>
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
index 0000000000000..515423a3d2691
--- /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 <cassert>
+
+constexpr void testProxy() {
+ // constructor value
+ {
+ Proxy<int> p{5};
+ assert(p.data == 5);
+ }
+
+ // constructor reference
+ {
+ int i = 5;
+ Proxy<int&> p{i};
+ assert(&p.data == &i);
+ }
+
+ // constructor conversion
+ {
+ int i = 5;
+ Proxy<int&> p1{i};
+ Proxy<int> p2 = p1;
+ assert(p2.data == 5);
+
+ Proxy<int&> p3{p2};
+ assert(&(p3.data) == &(p2.data));
+
+ MoveOnly m1{8};
+ Proxy<MoveOnly&&> p4 = std::move(m1);
+
+ Proxy<MoveOnly> p5 = std::move(p4);
+ assert(p5.data.get() == 8);
+ }
+
+ // assignment
+ {
+ Proxy<int> p1{5};
+ Proxy<int> p2{6};
+ p1 = p2;
+ assert(p1.data == 6);
+
+ MoveOnly m1{8};
+ Proxy<MoveOnly&&> p3 = std::move(m1);
+ Proxy<MoveOnly> p4{MoveOnly{9}};
+ p4 = std::move(p3);
+ assert(p4.data.get() == 8);
+
+ int i = 5, j = 6;
+ Proxy<int&> p5{i};
+ p5 = Proxy<int&>{j};
+ assert(p5.data == 6);
+ }
+
+ // const assignment
+ {
+ int i = 5;
+ int j = 6;
+ const Proxy<int&> p1{i};
+ const Proxy<int&> p2{j};
+ p1 = p2;
+ assert(i == 6);
+
+ MoveOnly m1{8};
+ MoveOnly m2{9};
+ Proxy<MoveOnly&&> p3 = std::move(m1);
+ const Proxy<MoveOnly&&> p4 = std::move(m2);
+ p4 = std::move(p3);
+ assert(p4.data.get() == 8);
+ }
+
+ // compare
+ {
+ Proxy<int> p1{5};
+ Proxy<int> p2{6};
+ assert(p1 != p2);
+ assert(p1 < p2);
+ }
+}
+
+static_assert(std::input_iterator<ProxyIterator<cpp20_input_iterator<int*>>>);
+static_assert(!std::forward_iterator<ProxyIterator<cpp20_input_iterator<int*>>>);
+
+static_assert(std::forward_iterator<ProxyIterator<forward_iterator<int*>>>);
+static_assert(!std::bidirectional_iterator<ProxyIterator<forward_iterator<int*>>>);
+
+static_assert(std::bidirectional_iterator<ProxyIterator<bidirectional_iterator<int*>>>);
+static_assert(!std::random_access_iterator<ProxyIterator<bidirectional_iterator<int*>>>);
+
+static_assert(std::random_access_iterator<ProxyIterator<random_access_iterator<int*>>>);
+static_assert(!std::contiguous_iterator<ProxyIterator<random_access_iterator<int*>>>);
+
+static_assert(std::random_access_iterator<ProxyIterator<contiguous_iterator<int*>>>);
+static_assert(!std::contiguous_iterator<ProxyIterator<contiguous_iterator<int*>>>);
+
+template <class Iter>
+constexpr void testInputIteratorOperation() {
+ int data[] = {1, 2};
+ ProxyIterator<Iter> iter{Iter{data}};
+ sentinel_wrapper<ProxyIterator<Iter>> sent{ProxyIterator<Iter>{Iter{data + 2}}};
+
+ std::same_as<Proxy<int&>> decltype(auto) result = *iter;
+ assert(result.data == 1);
+ auto& iter2 = ++iter;
+ static_assert(std::is_same_v<decltype(++iter), ProxyIterator<Iter>&>);
+ assert(&iter2 == &iter);
+ assert((*iter).data == 2);
+ ++iter;
+ assert(iter == sent);
+}
+
+template <class Iter>
+constexpr void testForwardIteratorOperation() {
+ int data[] = {1, 2};
+ ProxyIterator<Iter> iter{Iter{data}};
+
+ std::same_as<ProxyIterator<Iter>> decltype(auto) it2 = iter++;
+ assert((*it2).data == 1);
+ assert((*iter).data == 2);
+}
+
+template <class Iter>
+constexpr void testBidirectionalIteratorOperation() {
+ int data[] = {1, 2};
+ ProxyIterator<Iter> iter{Iter{data}};
+ ++iter;
+ assert((*iter).data == 2);
+
+ auto& iter2 = --iter;
+ static_assert(std::is_same_v<decltype(--iter), ProxyIterator<Iter>&>);
+ assert(&iter2 == &iter);
+ assert((*iter).data == 1);
+ ++iter;
+
+ std::same_as<ProxyIterator<Iter>> decltype(auto) iter3 = iter--;
+ assert((*iter).data == 1);
+ assert((*iter3).data == 2);
+}
+
+template <class Iter>
+constexpr void testRandomAccessIteratorOperation() {
+ int data[] = {1, 2, 3, 4, 5};
+ ProxyIterator<Iter> iter{Iter{data}};
+
+ auto& iter2 = iter += 2;
+ static_assert(std::is_same_v<decltype(iter += 2), ProxyIterator<Iter>&>);
+ assert(&iter2 == &iter);
+ assert((*iter).data == 3);
+
+ auto& iter3 = iter -= 1;
+ static_assert(std::is_same_v<decltype(iter -= 1), ProxyIterator<Iter>&>);
+ assert(&iter3 == &iter);
+ assert((*iter).data == 2);
+
+ std::same_as<Proxy<int&>> decltype(auto) r = iter[2];
+ assert(r.data == 4);
+
+ std::same_as<ProxyIterator<Iter>> decltype(auto) iter4 = iter - 1;
+ assert((*iter4).data == 1);
+
+ std::same_as<ProxyIterator<Iter>> decltype(auto) iter5 = iter4 + 2;
+ assert((*iter5).data == 3);
+
+ std::same_as<ProxyIterator<Iter>> decltype(auto) iter6 = 3 + iter4;
+ assert((*iter6).data == 4);
+
+ std::same_as<std::iter_
diff erence_t<Iter>> 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<cpp20_input_iterator<int*>>();
+ testInputIteratorOperation<forward_iterator<int*>>();
+ testInputIteratorOperation<bidirectional_iterator<int*>>();
+ testInputIteratorOperation<random_access_iterator<int*>>();
+ testInputIteratorOperation<contiguous_iterator<int*>>();
+ }
+
+ // forward iterator operations
+ {
+ testForwardIteratorOperation<forward_iterator<int*>>();
+ testForwardIteratorOperation<bidirectional_iterator<int*>>();
+ testForwardIteratorOperation<random_access_iterator<int*>>();
+ testForwardIteratorOperation<contiguous_iterator<int*>>();
+ }
+
+ // bidirectional iterator operations
+ {
+ testBidirectionalIteratorOperation<bidirectional_iterator<int*>>();
+ testBidirectionalIteratorOperation<random_access_iterator<int*>>();
+ testBidirectionalIteratorOperation<contiguous_iterator<int*>>();
+ }
+
+ // random access iterator operations
+ {
+ testRandomAccessIteratorOperation<random_access_iterator<int*>>();
+ testRandomAccessIteratorOperation<contiguous_iterator<int*>>();
+ }
+}
+
+constexpr void testProxyRange() {
+ int data[] = {3, 4, 5};
+ ProxyRange r{data};
+ std::same_as<ProxyIterator<int*>> decltype(auto) it = std::ranges::begin(r);
+ assert((*it).data == 3);
+ it += 3;
+ assert(it == std::ranges::end(r));
+}
+
+template <class Iter>
+concept StdMoveWorks = requires(std::iter_value_t<Iter> val, Iter iter) { val = std::move(*iter); };
+
+static_assert(StdMoveWorks<MoveOnly*>);
+static_assert(!StdMoveWorks<ProxyIterator<MoveOnly*>>);
+
+// although this "works" but it actually creates a copy instead of move
+static_assert(StdMoveWorks<ProxyIterator<int*>>);
+
+using std::swap;
+
+template <class Iter>
+concept SwapWorks = requires(Iter iter1, Iter iter2) { swap(*iter1, *iter2); };
+
+static_assert(SwapWorks<int*>);
+static_assert(!SwapWorks<ProxyIterator<int*>>);
+
+constexpr bool test() {
+ testProxy();
+ testProxyIterator();
+ testProxyRange();
+
+ // iter_move
+ {
+ MoveOnly data[] = {5, 6, 7};
+ ProxyRange r{data};
+ auto it = r.begin();
+ std::iter_value_t<decltype(it)> 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
index c4cecc411e94c..c222c93c524d3 100644
--- a/libcxx/test/support/test_iterators.h
+++ b/libcxx/test/support/test_iterators.h
@@ -12,11 +12,14 @@
#include <cassert>
#include <concepts>
#include <iterator>
+#include <ranges>
#include <stdexcept>
+#include <type_traits>
#include <utility>
#include "test_macros.h"
+
// This iterator meets C++20's Cpp17OutputIterator requirements, as described
// in Table 90 ([output.iterators]).
template <class It>
@@ -820,6 +823,300 @@ class Iterator {
} // 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 <class T>
+struct Proxy;
+
+template <class T>
+inline constexpr bool IsProxy = false;
+
+template <class T>
+inline constexpr bool IsProxy<Proxy<T>> = true;
+
+template <class T>
+struct Proxy {
+ T data;
+
+ constexpr T& getData() & { return data; }
+
+ constexpr const T& getData() const& { return data; }
+
+ constexpr T&& getData() && { return static_cast<T&&>(data); }
+
+ constexpr const T&& getData() const&& { return static_cast<const T&&>(data); }
+
+ template <class U>
+ requires std::constructible_from<T, U&&>
+ constexpr Proxy(U&& u) : data{std::forward<U>(u)} {}
+
+ // This constructor covers conversion from cvref of Proxy<U>, including non-const/const versions of copy/move constructor
+ template <class Other>
+ requires(IsProxy<std::decay_t<Other>> && std::constructible_from<T, decltype(std::declval<Other>().getData())>)
+ constexpr Proxy(Other&& other) : data{std::forward<Other>(other).getData()} {}
+
+ template <class Other>
+ requires(IsProxy<std::decay_t<Other>> && std::assignable_from<T&, decltype(std::declval<Other>().getData())>)
+ constexpr Proxy& operator=(Other&& other) {
+ data = std::forward<Other>(other).getData();
+ return *this;
+ }
+
+ // const assignment required to make ProxyIterator model std::indirectly_writable
+ template <class Other>
+ requires(IsProxy<std::decay_t<Other>> && std::assignable_from<const T&, decltype(std::declval<Other>().getData())>)
+ constexpr const Proxy& operator=(Other&& other) const {
+ data = std::forward<Other>(other).getData();
+ return *this;
+ }
+
+ // no specialised swap function that takes const Proxy& and no specialised const member swap
+ // Calling swap(Proxy<T>{}, Proxy<T>{}) 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<T>
+ = default;
+
+ friend constexpr auto operator<=>(const Proxy&, const Proxy&)
+ requires std::three_way_comparable<T>
+ = default;
+};
+
+// This is to make ProxyIterator model `std::indirectly_readable`
+template <class T, class U, template <class> class TQual, template <class> class UQual>
+ requires requires { typename std::common_reference_t<TQual<T>, UQual<U>>; }
+struct std::basic_common_reference<Proxy<T>, Proxy<U>, TQual, UQual> {
+ using type = Proxy<std::common_reference_t<TQual<T>, UQual<U>>>;
+};
+
+template <class T, class U>
+ requires requires { typename std::common_type_t<T, U>; }
+struct std::common_type<Proxy<T>, Proxy<U>> {
+ using type = Proxy<std::common_type_t<T, U>>;
+};
+
+// ProxyIterator
+// ======================================================================
+// It wraps `Base` iterator and when dereferenced it returns a Proxy<ref>
+// 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<int&>
+// iter_value_t -> Proxy<int>
+// iter_move -> Proxy<int&&>
+template <class Base>
+struct ProxyIteratorBase {};
+
+template <class Base>
+ requires std::derived_from<
+ typename std::iterator_traits<Base>::iterator_category,
+ std::input_iterator_tag>
+struct ProxyIteratorBase<Base> {
+ using iterator_category = std::input_iterator_tag;
+};
+
+template <std::input_iterator Base>
+consteval auto get_iterator_concept() {
+ if constexpr (std::random_access_iterator<Base>) {
+ return std::random_access_iterator_tag{};
+ } else if constexpr (std::bidirectional_iterator<Base>) {
+ return std::bidirectional_iterator_tag{};
+ } else if constexpr (std::forward_iterator<Base>) {
+ return std::forward_iterator_tag{};
+ } else {
+ return std::input_iterator_tag{};
+ }
+}
+
+template <std::input_iterator Base>
+struct ProxyIterator : ProxyIteratorBase<Base> {
+ Base base_;
+
+ using iterator_concept = decltype(get_iterator_concept<Base>());
+ using value_type = Proxy<std::iter_value_t<Base>>;
+ using
diff erence_type = std::iter_
diff erence_t<Base>;
+
+ ProxyIterator()
+ requires std::default_initializable<Base>
+ = default;
+
+ constexpr ProxyIterator(Base base) : base_{std::move(base)} {}
+
+ template <class T>
+ requires std::constructible_from<Base, T&&>
+ constexpr ProxyIterator(T&& t) : base_{std::forward<T>(t)} {}
+
+ friend constexpr decltype(auto) base(const ProxyIterator& p) { return base(p.base_); }
+
+ // Specialization of iter_move
+ // If operator* returns Proxy<Foo&>, iter_move will return Proxy<Foo&&>
+ // Note std::move(*it) returns Proxy<Foo&>&&, which is not what we want as
+ // it will likely result in a copy rather than a move
+ friend constexpr Proxy<std::iter_rvalue_reference_t<Base>> 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<std::iter_reference_t<Base>> 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<Base> {
+ return x.base_ == y.base_;
+ }
+
+ // to satisfy forward_iterator
+ constexpr ProxyIterator operator++(int)
+ requires std::forward_iterator<Base> {
+ auto tmp = *this;
+ ++*this;
+ return tmp;
+ }
+
+ // to satisfy bidirectional_iterator
+ constexpr ProxyIterator& operator--()
+ requires std::bidirectional_iterator<Base> {
+ --base_;
+ return *this;
+ }
+
+ constexpr ProxyIterator operator--(int)
+ requires std::bidirectional_iterator<Base> {
+ auto tmp = *this;
+ --*this;
+ return tmp;
+ }
+
+ // to satisfy random_access_iterator
+ constexpr ProxyIterator& operator+=(
diff erence_type n)
+ requires std::random_access_iterator<Base> {
+ base_ += n;
+ return *this;
+ }
+
+ constexpr ProxyIterator& operator-=(
diff erence_type n)
+ requires std::random_access_iterator<Base> {
+ base_ -= n;
+ return *this;
+ }
+
+ constexpr Proxy<std::iter_reference_t<Base>> operator[](
diff erence_type n) const
+ requires std::random_access_iterator<Base> {
+ return {base_[n]};
+ }
+
+ friend constexpr bool operator<(const ProxyIterator& x, const ProxyIterator& y)
+ requires std::random_access_iterator<Base> {
+ return x.base_ < y.base_;
+ }
+
+ friend constexpr bool operator>(const ProxyIterator& x, const ProxyIterator& y)
+ requires std::random_access_iterator<Base> {
+ return x.base_ > y.base_;
+ }
+
+ friend constexpr bool operator<=(const ProxyIterator& x, const ProxyIterator& y)
+ requires std::random_access_iterator<Base> {
+ return x.base_ <= y.base_;
+ }
+
+ friend constexpr bool operator>=(const ProxyIterator& x, const ProxyIterator& y)
+ requires std::random_access_iterator<Base> {
+ return x.base_ >= y.base_;
+ }
+
+ friend constexpr auto operator<=>(const ProxyIterator& x, const ProxyIterator& y)
+ requires(std::random_access_iterator<Base> && std::three_way_comparable<Base>) {
+ return x.base_ <=> y.base_;
+ }
+
+ friend constexpr ProxyIterator operator+(const ProxyIterator& x,
diff erence_type n)
+ requires std::random_access_iterator<Base> {
+ return ProxyIterator{x.base_ + n};
+ }
+
+ friend constexpr ProxyIterator operator+(
diff erence_type n, const ProxyIterator& x)
+ requires std::random_access_iterator<Base> {
+ return ProxyIterator{n + x.base_};
+ }
+
+ friend constexpr ProxyIterator operator-(const ProxyIterator& x,
diff erence_type n)
+ requires std::random_access_iterator<Base> {
+ return ProxyIterator{x.base_ - n};
+ }
+
+ friend constexpr
diff erence_type operator-(const ProxyIterator& x, const ProxyIterator& y)
+ requires std::random_access_iterator<Base> {
+ return x.base_ - y.base_;
+ }
+};
+
+static_assert(std::indirectly_readable<ProxyIterator<int*>>);
+static_assert(std::indirectly_writable<ProxyIterator<int*>, Proxy<int>>);
+
+template <class BaseSent>
+struct ProxySentinel {
+ BaseSent base_;
+
+ ProxySentinel() = default;
+ constexpr ProxySentinel(BaseSent base) : base_{std::move(base)} {}
+
+ template <class Base>
+ requires std::equality_comparable_with<Base, BaseSent>
+ friend constexpr bool operator==(const ProxyIterator<Base>& p, const ProxySentinel& sent) {
+ return p.base_ == sent.base_;
+ }
+};
+
+#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+template <std::ranges::input_range Base>
+ requires std::ranges::view<Base>
+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<const Base> {
+ return ProxyIterator{std::ranges::begin(base_)};
+ }
+
+ constexpr auto end() const
+ requires std::ranges::input_range<const Base> {
+ return ProxySentinel{std::ranges::end(base_)};
+ }
+};
+
+template <std::ranges::input_range R>
+ requires std::ranges::viewable_range<R&&>
+ProxyRange(R&&) -> ProxyRange<std::views::all_t<R&&>>;
+#endif // !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
#endif // TEST_STD_VER > 17
#endif // SUPPORT_TEST_ITERATORS_H
More information about the libcxx-commits
mailing list