[libcxx-commits] [libcxx] b8fd0c1 - [libc++][pstl] Implementation of parallel std::is_sorted_until() based on std::adjacent_find() (#213445)

via libcxx-commits libcxx-commits at lists.llvm.org
Mon Aug 3 23:00:09 PDT 2026


Author: Michael G. Kazakov
Date: 2026-08-04T14:00:04+08:00
New Revision: b8fd0c1cbb1867ddf74eb80ced58c5d289a56321

URL: https://github.com/llvm/llvm-project/commit/b8fd0c1cbb1867ddf74eb80ced58c5d289a56321
DIFF: https://github.com/llvm/llvm-project/commit/b8fd0c1cbb1867ddf74eb80ced58c5d289a56321.diff

LOG: [libc++][pstl] Implementation of parallel std::is_sorted_until() based on std::adjacent_find() (#213445)

This PR adds implementation of a parallel `std::is_sorted_until()` based
on the parallel `std::adjacent_find()` and rebases the parallel
`std::is_sorted()` onto `std::is_sorted_until()`.

The implementation is effectively a one-liner:
```c++
// Find the first pair of adjacent elements that are not in sorted order,
// i.e. comp(rhs, lhs) is true.
auto res = AdjacentFind()(policy, std::move(first), last, [&](Ref lhs, Ref rhs) {
    return comp(rhs, lhs);
});
```

Included tests check that:
- Semantics of the iterator-only version is correct.
- Semantics of the predicated version is correct.
- The functions correctly SFINAE out when the first argument is not an
execution policy.
- The `noexcept` policy is followed.
- The `nodiscard` policy is followed.
- `static_assert` verifies iterators' categories.

Part of #99938.

Added: 
    libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_until.pass.cpp
    libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_until_comp.pass.cpp

Modified: 
    libcxx/include/__algorithm/pstl.h
    libcxx/include/__pstl/backend_fwd.h
    libcxx/include/__pstl/backends/default.h
    libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
    libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
    libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index 2c628c4c8698f..d27044ecdc8b9 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -921,6 +921,31 @@ is_sorted(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterato
       std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), std::move(__comp));
 }
 
+template <class _ExecutionPolicy,
+          class _ForwardIterator,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _ForwardIterator
+is_sorted_until(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "is_sorted_until requires ForwardIterators");
+  using _Implementation = __pstl::__dispatch<__pstl::__is_sorted_until, __pstl::__current_configuration, _RawPolicy>;
+  return __pstl::__handle_exception<_Implementation>(
+      std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), less{});
+}
+
+template <class _ExecutionPolicy,
+          class _ForwardIterator,
+          class _Comp,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _ForwardIterator
+is_sorted_until(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Comp __comp) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "is_sorted_until requires ForwardIterators");
+  using _Implementation = __pstl::__dispatch<__pstl::__is_sorted_until, __pstl::__current_configuration, _RawPolicy>;
+  return __pstl::__handle_exception<_Implementation>(
+      std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), std::move(__comp));
+}
+
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP_HAS_EXPERIMENTAL_PSTL && _LIBCPP_STD_VER >= 17

diff  --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index 248c90ba16596..e83375d1c0c74 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -323,6 +323,12 @@ struct __is_sorted;
 // optional<bool>
 // operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Comp&& __comp) const noexcept;
 
+template <class _Backend, class _ExecutionPolicy>
+struct __is_sorted_until;
+// template <class _Policy, class _ForwardIterator, class _Comp>
+// optional<_ForwardIterator>
+// operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Comp&& __comp) const noexcept;
+
 template <class _Backend, class _ExecutionPolicy>
 struct __adjacent_
diff erence;
 // template <class _Policy, class _ForwardIterator1, class _ForwardIterator2, class _BinaryOperation>

diff  --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index 02d20930d62f8..d2c382e1d479f 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -69,6 +69,8 @@ namespace __pstl {
 // mismatch family
 // ---------------
 // - adjacent_find
+// - is_sorted
+// - is_sorted_until
 // - lexicographical_compare
 // - mismatch_3leg
 //
@@ -100,7 +102,6 @@ namespace __pstl {
 // - count
 // - equal(3 legs)
 // - equal
-// - is_sorted
 // - reduce
 //
 // transform and transform_binary family
@@ -326,6 +327,42 @@ struct __adjacent_find<__default_backend_tag, _ExecutionPolicy> {
   }
 };
 
+template <class _ExecutionPolicy>
+struct __is_sorted_until<__default_backend_tag, _ExecutionPolicy> {
+  template <class _Policy, class _ForwardIterator, class _Comp>
+  optional<_ForwardIterator>
+  operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Comp&& __comp) const noexcept {
+    using _AdjacentFind = __dispatch<__adjacent_find, __current_configuration, _ExecutionPolicy>;
+    using _Ref          = __iterator_reference<_ForwardIterator>;
+    // Find the first pair of adjacent elements that are not in sorted order (i.e. __comp(__rhs, __lhs) is true).
+    auto __res = _AdjacentFind()(__policy, std::move(__first), __last, [&](_Ref __lhs, _Ref __rhs) {
+      return __comp(__rhs, __lhs);
+    });
+    if (!__res) {
+      return nullopt; // Failed to run the algorithm, propagate the error.
+    }
+    if (*__res == __last) {
+      return __last; // Range is sorted, return __last.
+    }
+    ++*__res; // Advance the iterator to the first unsorted element.
+    return *__res;
+  }
+};
+
+template <class _ExecutionPolicy>
+struct __is_sorted<__default_backend_tag, _ExecutionPolicy> {
+  template <class _Policy, class _ForwardIterator, class _Comp>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI optional<bool>
+  operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Comp&& __comp) const noexcept {
+    using _IsSortedUntil = __dispatch<__is_sorted_until, __current_configuration, _ExecutionPolicy>;
+    auto __res           = _IsSortedUntil()(__policy, std::move(__first), __last, std::forward<_Comp>(__comp));
+    if (!__res) {
+      return nullopt; // Failed to run the algorithm, propagate the error.
+    }
+    return *__res == __last; // If the first unsorted element is the end of the range, the range is sorted.
+  }
+};
+
 //////////////////////////////////////////////////////////////
 // for_each family
 //////////////////////////////////////////////////////////////
@@ -536,35 +573,6 @@ struct __reduce<__default_backend_tag, _ExecutionPolicy> {
   }
 };
 
-template <class _ExecutionPolicy>
-struct __is_sorted<__default_backend_tag, _ExecutionPolicy> {
-  template <class _Policy, class _ForwardIterator, class _Comp>
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI optional<bool>
-  operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Comp&& __comp) const noexcept {
-    if constexpr (__has_bidirectional_iterator_category<_ForwardIterator>::value) {
-      if (__first == __last)
-        return true; // Empty, sorted by definition
-      _ForwardIterator __first2 = std::next(__first);
-      if (__first2 == __last)
-        return true; // Only one element, sorted by definition
-      --__last;      // Make two iterator ranges: [__first, __first + n - 1) and [__first + 1, __first + n)
-      using _TransformReduce = __dispatch<__transform_reduce_binary, __current_configuration, _ExecutionPolicy>;
-      using _Ref             = __iterator_reference<_ForwardIterator>;
-      return _TransformReduce()(
-          __policy,
-          std::move(__first),
-          std::move(__last),
-          std::move(__first2),
-          true,
-          std::logical_and{},
-          [&](_Ref __left, _Ref __right) -> bool { return !__comp(__right, __left); });
-    } else {
-      // Currently anything outside bidirectional iterators has to be processed serially
-      return std::is_sorted(std::move(__first), std::move(__last), std::forward<_Comp>(__comp));
-    }
-  }
-};
-
 //////////////////////////////////////////////////////////////
 // transform family
 //////////////////////////////////////////////////////////////

diff  --git a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
index a8ab2501517e0..e61c562025fa0 100644
--- a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
@@ -140,6 +140,18 @@ void f(non_forward_iterator non_fwd,
         pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: is_partitioned}}
   }
 
+  {
+    (void)std::is_sorted(pol, non_fwd, non_fwd);       // expected-error@*:* {{static assertion failed: is_sorted}}
+    (void)std::is_sorted(pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: is_sorted}}
+  }
+
+  {
+    (void)std::is_sorted_until(
+        pol, non_fwd, non_fwd); // expected-error@*:* {{static assertion failed: is_sorted_until}}
+    (void)std::is_sorted_until(
+        pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: is_sorted_until}}
+  }
+
   {
     (void)std::merge(pol, non_fwd, non_fwd, it, it, out); // expected-error@*:* {{static assertion failed: merge}}
     (void)std::merge(pol, it, it, non_fwd, non_fwd, out); // expected-error@*:* {{static assertion failed: merge}}

diff  --git a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
index 0efb6ffe6b0cd..37e843eb90fe1 100644
--- a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
@@ -58,6 +58,10 @@ void test() {
   // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::is_sorted(std::execution::par, std::begin(a), std::end(a), pred2);
   // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::is_sorted_until(std::execution::par, std::begin(a), std::end(a));
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::is_sorted_until(std::execution::par, std::begin(a), std::end(a), pred2);
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::reverse_copy(std::execution::par, std::begin(a), std::end(a), std::begin(b));
   // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::adjacent_
diff erence(std::execution::par, std::begin(a), std::end(a), std::begin(b));

diff  --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_until.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_until.pass.cpp
new file mode 100644
index 0000000000000..125e49226c568
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_until.pass.cpp
@@ -0,0 +1,251 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// template<class ExecutionPolicy, class ForwardIterator>
+// ForwardIterator is_sorted_until(ExecutionPolicy&& exec,
+//                                 ForwardIterator first, ForwardIterator last);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <numeric>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "type_algorithms.h"
+#include "runway_sample.h"
+
+EXECUTION_POLICY_SFINAE_TEST(is_sorted_until);
+
+static_assert(sfinae_test_is_sorted_until<int, int*, int*>);
+static_assert(!sfinae_test_is_sorted_until<std::execution::parallel_policy, int*, int*>);
+
+struct NonCopyableNonAssignable {
+  NonCopyableNonAssignable() = delete;
+  explicit NonCopyableNonAssignable(int i) : i_(i) {}
+  NonCopyableNonAssignable(const NonCopyableNonAssignable&)            = delete;
+  NonCopyableNonAssignable(NonCopyableNonAssignable&&)                 = delete;
+  NonCopyableNonAssignable& operator=(const NonCopyableNonAssignable&) = delete;
+  NonCopyableNonAssignable& operator=(NonCopyableNonAssignable&&)      = delete;
+  bool operator<(const NonCopyableNonAssignable& other) const { return i_ < other.i_; }
+
+private:
+  int i_;
+};
+
+template <class Iter>
+struct Test {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    { // check the return types
+      int a[]  = {0};
+      auto res = std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::begin(a)));
+      static_assert(std::is_same_v<decltype(res), Iter>);
+    }
+    { // empty range
+      int a[]  = {0};
+      auto res = std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::begin(a)));
+      assert(res == Iter(std::begin(a)));
+    }
+    { // single element range
+      int a[]  = {0};
+      auto res = std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)));
+      assert(res == Iter(std::end(a)));
+    }
+    { // two element range, equal - sorted
+      int a[] = {0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // two element range, ascending - sorted
+      int a[] = {0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // two element range, descending - not sorted
+      int a[] = {1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 1));
+    }
+    { // two element range, equal - sorted
+      int a[] = {1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // three element range, equal - sorted
+      int a[] = {0, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // three element range, ascending - sorted
+      int a[] = {0, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // three element range, not sorted
+      int a[] = {0, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 2));
+    }
+    { // three element range, ascending - sorted
+      int a[] = {0, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // three element range, descending - not sorted
+      int a[] = {1, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 1));
+    }
+    { // three element range, not sorted
+      int a[] = {1, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 1));
+    }
+    { // three element range, not sorted
+      int a[] = {1, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 2));
+    }
+    { // three element range, equal - sorted
+      int a[] = {1, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // four element range, equal - sorted
+      int a[] = {0, 0, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // four element range, ascending - sorted
+      int a[] = {0, 0, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // four element range, not sorted
+      int a[] = {0, 0, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 3));
+    }
+    { // four element range, ascending - sorted
+      int a[] = {0, 0, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // four element range, not sorted
+      int a[] = {0, 1, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 2));
+    }
+    { // four element range, not sorted
+      int a[] = {0, 1, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 2));
+    }
+    { // four element range, not sorted
+      int a[] = {0, 1, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 3));
+    }
+    { // four element range, ascending - sorted
+      int a[] = {0, 1, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // four element range, descending - not sorted
+      int a[] = {1, 0, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 1));
+    }
+    { // four element range, not sorted
+      int a[] = {1, 0, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 1));
+    }
+    { // four element range, not sorted
+      int a[] = {1, 0, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 1));
+    }
+    { // four element range, not sorted
+      int a[] = {1, 0, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 1));
+    }
+    { // four element range, descending - not sorted
+      int a[] = {1, 1, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 2));
+    }
+    { // four element range, not sorted
+      int a[] = {1, 1, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 2));
+    }
+    { // four element range, descending - not sorted
+      int a[] = {1, 1, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 3));
+    }
+    { // four element range, equal - sorted
+      int a[] = {1, 1, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // four element range, min/max - sorted
+      int a[] = {std::numeric_limits<int>::min(),
+                 std::numeric_limits<int>::min(),
+                 std::numeric_limits<int>::max(),
+                 std::numeric_limits<int>::max()};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // four element range, min/max - not sorted
+      int a[] = {std::numeric_limits<int>::min(),
+                 std::numeric_limits<int>::max(),
+                 std::numeric_limits<int>::min(),
+                 std::numeric_limits<int>::max()};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 2));
+    }
+    { // seven element range, min/max - sorted
+      int a[] = {
+          std::numeric_limits<int>::min(),
+          std::numeric_limits<int>::min() / 2,
+          -1,
+          0,
+          1,
+          std::numeric_limits<int>::max() / 2,
+          std::numeric_limits<int>::max()};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::end(a)));
+    }
+    { // seven element range, min/max - not sorted
+      int a[] = {
+          std::numeric_limits<int>::min(),
+          std::numeric_limits<int>::min() / 2,
+          1,
+          0,
+          -1,
+          std::numeric_limits<int>::max() / 2,
+          std::numeric_limits<int>::max()};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 3));
+    }
+    { // many elements, ordering breaks at various sampled positions
+      int a[1073];
+      std::iota(std::begin(a), std::end(a), 0);
+      runway_sample(std::size(a), [&](size_t i) {
+        if (i == 0) {
+          return; // sorted range cannot be broken at the first element
+        }
+        a[i] = -1;
+        assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + i));
+        a[i] = static_cast<int>(i);
+      });
+    }
+  }
+};
+
+template <class Iter>
+struct TestNonCopyableNonAssignable {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    // Four elements, unsorted
+    NonCopyableNonAssignable a[] = {
+        NonCopyableNonAssignable(0),
+        NonCopyableNonAssignable(1),
+        NonCopyableNonAssignable(2),
+        NonCopyableNonAssignable(0)};
+    assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a))) == Iter(std::begin(a) + 3));
+    assert(std::is_sorted_until(policy, Iter(std::begin(a) + 1), Iter(std::end(a))) == Iter(std::begin(a) + 3));
+    assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::begin(a) + 3)) == Iter(std::begin(a) + 3));
+  }
+};
+
+int main(int, char**) {
+  types::for_each(types::forward_iterator_list<int*>{}, TestIteratorWithPolicies<Test>{});
+  types::for_each(types::forward_iterator_list<const NonCopyableNonAssignable*>{},
+                  TestIteratorWithPolicies<TestNonCopyableNonAssignable>{});
+  return 0;
+}

diff  --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_until_comp.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_until_comp.pass.cpp
new file mode 100644
index 0000000000000..09cb11994a3db
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_until_comp.pass.cpp
@@ -0,0 +1,260 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// template<class ExecutionPolicy, class ForwardIterator, class Comp>
+// ForwardIterator is_sorted_until(ExecutionPolicy&& exec,
+//                                 ForwardIterator first, ForwardIterator last,
+//                                 Comp comp);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <numeric>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "type_algorithms.h"
+#include "runway_sample.h"
+
+EXECUTION_POLICY_SFINAE_TEST(is_sorted_until);
+
+static_assert(sfinae_test_is_sorted_until<int, int*, int*, bool (*)(int, int)>);
+static_assert(!sfinae_test_is_sorted_until<std::execution::parallel_policy, int*, int*, bool (*)(int, int)>);
+
+struct NonCopyableNonAssignable {
+  NonCopyableNonAssignable() = delete;
+  explicit NonCopyableNonAssignable(int i) : i_(i) {}
+  NonCopyableNonAssignable(const NonCopyableNonAssignable&)            = delete;
+  NonCopyableNonAssignable(NonCopyableNonAssignable&&)                 = delete;
+  NonCopyableNonAssignable& operator=(const NonCopyableNonAssignable&) = delete;
+  NonCopyableNonAssignable& operator=(NonCopyableNonAssignable&&)      = delete;
+  int value() const { return i_; }
+
+private:
+  int i_;
+};
+
+struct Comp {
+  bool operator()(int lhs, int rhs) const { return lhs > rhs; }
+  bool operator()(const NonCopyableNonAssignable& lhs, const NonCopyableNonAssignable& rhs) const {
+    return lhs.value() > rhs.value();
+  }
+};
+
+template <class Iter>
+struct Test {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    { // check the return types
+      int a[]  = {0};
+      auto res = std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::begin(a)), Comp{});
+      static_assert(std::is_same_v<decltype(res), Iter>);
+    }
+    { // empty range
+      int a[]  = {0};
+      auto res = std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::begin(a)), Comp{});
+      assert(res == Iter(std::begin(a)));
+    }
+    { // single element range
+      int a[]  = {0};
+      auto res = std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{});
+      assert(res == Iter(std::end(a)));
+    }
+    { // two element range, equal - sorted
+      int a[] = {0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // two element range, ascending - unsorted
+      int a[] = {0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // two element range, descending - sorted
+      int a[] = {1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // two element range, equal - sorted
+      int a[] = {1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // three element range, equal - sorted
+      int a[] = {0, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // three element range, ascending - unsorted
+      int a[] = {0, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 2));
+    }
+    { // three element range, not sorted
+      int a[] = {0, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // three element range, ascending - unsorted
+      int a[] = {0, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // three element range, descending -  sorted
+      int a[] = {1, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // three element range, not sorted
+      int a[] = {1, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 2));
+    }
+    { // three element range, sorted
+      int a[] = {1, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // three element range, equal - sorted
+      int a[] = {1, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // four element range, equal - sorted
+      int a[] = {0, 0, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // four element range, ascending - unsorted
+      int a[] = {0, 0, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 3));
+    }
+    { // four element range, not sorted
+      int a[] = {0, 0, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 2));
+    }
+    { // four element range, ascending - unsorted
+      int a[] = {0, 0, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 2));
+    }
+    { // four element range, not sorted
+      int a[] = {0, 1, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // four element range, not sorted
+      int a[] = {0, 1, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // four element range, not sorted
+      int a[] = {0, 1, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // four element range, ascending - unsorted
+      int a[] = {0, 1, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // four element range, descending - sorted
+      int a[] = {1, 0, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // four element range, not sorted
+      int a[] = {1, 0, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 3));
+    }
+    { // four element range, not sorted
+      int a[] = {1, 0, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 2));
+    }
+    { // four element range, not sorted
+      int a[] = {1, 0, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 2));
+    }
+    { // four element range, descending - sorted
+      int a[] = {1, 1, 0, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // four element range, not sorted
+      int a[] = {1, 1, 0, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 3));
+    }
+    { // four element range, descending - sorted
+      int a[] = {1, 1, 1, 0};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // four element range, equal - sorted
+      int a[] = {1, 1, 1, 1};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::end(a)));
+    }
+    { // four element range, min/max - unsorted
+      int a[] = {std::numeric_limits<int>::min(),
+                 std::numeric_limits<int>::min(),
+                 std::numeric_limits<int>::max(),
+                 std::numeric_limits<int>::max()};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 2));
+    }
+    { // four element range, min/max - not sorted
+      int a[] = {std::numeric_limits<int>::min(),
+                 std::numeric_limits<int>::max(),
+                 std::numeric_limits<int>::min(),
+                 std::numeric_limits<int>::max()};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // seven element range, min/max - sorted
+      int a[] = {
+          std::numeric_limits<int>::min(),
+          std::numeric_limits<int>::min() / 2,
+          -1,
+          0,
+          1,
+          std::numeric_limits<int>::max() / 2,
+          std::numeric_limits<int>::max()};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // seven element range, min/max - not sorted
+      int a[] = {
+          std::numeric_limits<int>::min(),
+          std::numeric_limits<int>::min() / 2,
+          1,
+          0,
+          -1,
+          std::numeric_limits<int>::max() / 2,
+          std::numeric_limits<int>::max()};
+      assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 1));
+    }
+    { // many elements, ordering breaks at various sampled positions
+      int a[1073];
+      std::generate(std::begin(a), std::end(a), [n = 0]() mutable { return n--; });
+      runway_sample(std::size(a), [&](size_t i) {
+        if (i == 0) {
+          return; // sorted range cannot be broken at the first element
+        }
+        a[i] = 1;
+        assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + i));
+        a[i] = -static_cast<int>(i);
+      });
+    }
+  }
+};
+
+template <class Iter>
+struct TestNonCopyableNonAssignable {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    // Four elements, unsorted
+    NonCopyableNonAssignable a[] = {
+        NonCopyableNonAssignable(2),
+        NonCopyableNonAssignable(2),
+        NonCopyableNonAssignable(1),
+        NonCopyableNonAssignable(3)};
+    assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 3));
+    assert(std::is_sorted_until(policy, Iter(std::begin(a) + 1), Iter(std::end(a)), Comp{}) == Iter(std::begin(a) + 3));
+    assert(std::is_sorted_until(policy, Iter(std::begin(a)), Iter(std::begin(a) + 3), Comp{}) ==
+           Iter(std::begin(a) + 3));
+  }
+};
+
+int main(int, char**) {
+  types::for_each(types::forward_iterator_list<int*>{}, TestIteratorWithPolicies<Test>{});
+  types::for_each(types::forward_iterator_list<const NonCopyableNonAssignable*>{},
+                  TestIteratorWithPolicies<TestNonCopyableNonAssignable>{});
+  return 0;
+}

diff  --git a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
index 66cd7ff632ef6..063210a13af7e 100644
--- a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
+++ b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
@@ -370,6 +370,14 @@ int main(int, char**) {
         assert_non_throwing([=, &policy] {
           (void)std::is_sorted(policy, std::move(first1), std::move(last1), compare);
         });
+
+        // is_sorted_until(first, last)
+        assert_non_throwing([=, &policy] { (void)std::is_sorted_until(policy, std::move(first1), std::move(last1)); });
+
+        // is_sorted_until(first, last, comp)
+        assert_non_throwing([=, &policy] {
+          (void)std::is_sorted_until(policy, std::move(first1), std::move(last1), compare);
+        });
       }
 
       {


        


More information about the libcxx-commits mailing list