[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