[libcxx-commits] [libcxx] [libc++][pstl] Generic implementation of parallel std::is_sorted (PR #176129)

Michael G. Kazakov via libcxx-commits libcxx-commits at lists.llvm.org
Sat Jan 24 02:18:01 PST 2026


https://github.com/mikekazakov updated https://github.com/llvm/llvm-project/pull/176129

>From dc01e44005393bb688a84c286f1cce30d1838898 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Thu, 15 Jan 2026 10:27:20 +0000
Subject: [PATCH 1/6] Generic implementation of parallel std::is_sorted based
 on std::transform_reduce

---
 libcxx/include/__algorithm/pstl.h             |  24 +++
 libcxx/include/__pstl/backend_fwd.h           |   6 +
 libcxx/include/__pstl/backends/default.h      |  30 +++
 .../is.sorted/pstl.is_sorted.pass.cpp         | 188 +++++++++++++++++
 .../is.sorted/pstl.is_sorted_comp.pass.cpp    | 189 ++++++++++++++++++
 5 files changed, 437 insertions(+)
 create mode 100644 libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp
 create mode 100644 libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp

diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index 7169dd85df602..a79bac3200d6b 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -654,6 +654,30 @@ _LIBCPP_HIDE_FROM_ABI _ForwardOutIterator transform(
       std::move(__op));
 }
 
+template <class _ExecutionPolicy,
+          class _ForwardIterator,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+_LIBCPP_HIDE_FROM_ABI bool is_sorted(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "is_sorted requires ForwardIterators");
+  using _Implementation = __pstl::__dispatch<__pstl::__is_sorted, __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>
+_LIBCPP_HIDE_FROM_ABI bool
+is_sorted(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Comp __comp) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "is_sorted requires ForwardIterators");
+  using _Implementation = __pstl::__dispatch<__pstl::__is_sorted, __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 a7d53b6a1c989..a52e6db954d0c 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -297,6 +297,12 @@ struct __reduce;
 // operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last,
 //                       _Tp __init, _BinaryOperation __op) const noexcept;
 
+template <class _Backend, class _ExecutionPolicy>
+struct __is_sorted;
+// template <class _Policy, class _ForwardIterator, class _Comp>
+// optional<bool>
+// operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Comp&& __comp) const noexcept;
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index 43b1f1ce3870a..9fa642b080f64 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -13,6 +13,7 @@
 #include <__algorithm/equal.h>
 #include <__algorithm/fill_n.h>
 #include <__algorithm/for_each_n.h>
+#include <__algorithm/is_sorted.h>
 #include <__config>
 #include <__functional/identity.h>
 #include <__functional/not_fn.h>
@@ -388,6 +389,35 @@ 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_random_access_iterator_category<_ForwardIterator>::value) {
+      if (__first == __last)
+        return true; // Empty, sorted by definition
+      _ForwardIterator __first2 = __first + 1;
+      if (__first2 == __last)
+        return true; // Only one element, sorted by definition
+      --__last;
+      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 __first, _Ref __second) -> bool { return !__comp(__second, __first); });
+    } else {
+      // Currently anything outside random access 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/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp
new file mode 100644
index 0000000000000..9bffa588f0858
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp
@@ -0,0 +1,188 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// template<class ExecutionPolicy, class ForwardIterator,
+//   bool is_sorted(ExecutionPolicy&& exec,
+//                  ForwardIterator first, ForwardIterator last);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <numeric>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class Iter>
+struct Test {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    {
+      int a[]     = {0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a)));
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+
+    {
+      int a[]     = {0, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+
+    {
+      int a[]     = {0, 0, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 0, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 0, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 0, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 1, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 1, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 1, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {0, 1, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 0, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 0, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 0, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 0, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 1, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 1, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 1, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {1, 1, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+  }
+};
+
+int main(int, char**) {
+  types::for_each(types::concatenate_t<types::forward_iterator_list<int*>,
+                                       types::bidirectional_iterator_list<int*>,
+                                       types::random_access_iterator_list<int*>>{},
+                  TestIteratorWithPolicies<Test>{});
+
+  return 0;
+}
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp
new file mode 100644
index 0000000000000..aa57678a5ec2a
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp
@@ -0,0 +1,189 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// template<class ExecutionPolicy, class ForwardIterator, class Comp>
+//   bool is_sorted(ExecutionPolicy&& exec,
+//                  ForwardIterator first, ForwardIterator last,
+//                  Comp comp);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <numeric>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class Iter>
+struct Test {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    {
+      int a[]     = {0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a)));
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+
+    {
+      int a[]     = {0, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+
+    {
+      int a[]     = {0, 0, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 0, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 0, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 0, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 1, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 1, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 1, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {0, 1, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 0, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 0, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 0, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 0, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 1, 0, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 1, 0, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 1, 1, 0};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {1, 1, 1, 1};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+  }
+};
+
+int main(int, char**) {
+  types::for_each(types::concatenate_t<types::forward_iterator_list<int*>,
+                                       types::bidirectional_iterator_list<int*>,
+                                       types::random_access_iterator_list<int*>>{},
+                  TestIteratorWithPolicies<Test>{});
+
+  return 0;
+}

>From 695b416e9aae9d7b24c8757699cdb2216bf8af91 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Thu, 15 Jan 2026 20:02:53 +0000
Subject: [PATCH 2/6] Changed the arguments names to prevent shadowing

---
 libcxx/include/__pstl/backends/default.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index 9fa642b080f64..a555a3f7878a7 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -410,7 +410,7 @@ struct __is_sorted<__default_backend_tag, _ExecutionPolicy> {
           std::move(__first2),
           true,
           std::logical_and{},
-          [&](_Ref __first, _Ref __second) -> bool { return !__comp(__second, __first); });
+          [&](_Ref __left, _Ref __right) -> bool { return !__comp(__right, __left); });
     } else {
       // Currently anything outside random access iterators has to be processed serially
       return std::is_sorted(std::move(__first), std::move(__last), std::forward<_Comp>(__comp));

>From 4301d18c1df0f559811decfe8c6e175142c00f03 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Tue, 20 Jan 2026 21:28:42 +0000
Subject: [PATCH 3/6] Relaxed the iterator requirement to bidirectional, added
 more diverse test cases

---
 libcxx/include/__pstl/backends/default.h      |  8 ++--
 .../is.sorted/pstl.is_sorted.pass.cpp         | 43 ++++++++++++++++++-
 .../is.sorted/pstl.is_sorted_comp.pass.cpp    | 40 +++++++++++++++++
 3 files changed, 87 insertions(+), 4 deletions(-)

diff --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index a555a3f7878a7..8c84225ad6c11 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -81,6 +81,7 @@ namespace __pstl {
 // - count
 // - equal(3 legs)
 // - equal
+// - is_sorted
 // - reduce
 //
 // transform and transform_binary family
@@ -394,13 +395,14 @@ 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_random_access_iterator_category<_ForwardIterator>::value) {
+    if constexpr (__has_bidirectional_iterator_category<_ForwardIterator>::value) {
       if (__first == __last)
         return true; // Empty, sorted by definition
-      _ForwardIterator __first2 = __first + 1;
+      _ForwardIterator __first2 = __first;
+      ++__first2; // __first2 = __first + 1
       if (__first2 == __last)
         return true; // Only one element, sorted by definition
-      --__last;
+      --__last;      // __last = __last - 1
       using _TransformReduce = __dispatch<__transform_reduce_binary, __current_configuration, _ExecutionPolicy>;
       using _Ref             = __iterator_reference<_ForwardIterator>;
       return _TransformReduce()(
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp
index 9bffa588f0858..a7708721c6035 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp
@@ -10,7 +10,7 @@
 
 // UNSUPPORTED: libcpp-has-no-incomplete-pstl
 
-// template<class ExecutionPolicy, class ForwardIterator,
+// template<class ExecutionPolicy, class ForwardIterator>
 //   bool is_sorted(ExecutionPolicy&& exec,
 //                  ForwardIterator first, ForwardIterator last);
 
@@ -18,6 +18,7 @@
 #include <cassert>
 #include <functional>
 #include <numeric>
+#include <limits>
 
 #include "test_execution_policies.h"
 #include "test_iterators.h"
@@ -175,6 +176,46 @@ struct Test {
       unsigned sa = sizeof(a) / sizeof(a[0]);
       assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
     }
+    {
+      int a[]     = {std::numeric_limits<int>::min(),
+                     std::numeric_limits<int>::min(),
+                     std::numeric_limits<int>::max(),
+                     std::numeric_limits<int>::max()};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      int a[]     = {std::numeric_limits<int>::min(),
+                     std::numeric_limits<int>::max(),
+                     std::numeric_limits<int>::min(),
+                     std::numeric_limits<int>::max()};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      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()};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
+    {
+      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()};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa)));
+    }
   }
 };
 
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp
index aa57678a5ec2a..b7b01188d7f0b 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp
@@ -176,6 +176,46 @@ struct Test {
       unsigned sa = sizeof(a) / sizeof(a[0]);
       assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
     }
+    {
+      int a[]     = {std::numeric_limits<int>::max(),
+                     std::numeric_limits<int>::max(),
+                     std::numeric_limits<int>::min(),
+                     std::numeric_limits<int>::min()};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[]     = {std::numeric_limits<int>::min(),
+                     std::numeric_limits<int>::max(),
+                     std::numeric_limits<int>::min(),
+                     std::numeric_limits<int>::max()};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[] = {
+          std::numeric_limits<int>::max(),
+          std::numeric_limits<int>::max() / 2,
+          1,
+          0,
+          -1,
+          std::numeric_limits<int>::min() / 2,
+          std::numeric_limits<int>::min()};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
+    {
+      int a[] = {
+          std::numeric_limits<int>::max(),
+          std::numeric_limits<int>::max() / 2,
+          -1,
+          0,
+          1,
+          std::numeric_limits<int>::min() / 2,
+          std::numeric_limits<int>::min()};
+      unsigned sa = sizeof(a) / sizeof(a[0]);
+      assert(!std::is_sorted(policy, Iter(a), Iter(a + sa), std::greater<int>()));
+    }
   }
 };
 

>From 4904023e31caf1d9ff253421b77c2f1869c2aaf9 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Tue, 20 Jan 2026 21:55:33 +0000
Subject: [PATCH 4/6] Updated the comment

---
 libcxx/include/__pstl/backends/default.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index 8c84225ad6c11..70ca0cec094c1 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -414,7 +414,7 @@ struct __is_sorted<__default_backend_tag, _ExecutionPolicy> {
           std::logical_and{},
           [&](_Ref __left, _Ref __right) -> bool { return !__comp(__right, __left); });
     } else {
-      // Currently anything outside random access iterators has to be processed serially
+      // Currently anything outside bidirectional iterators has to be processed serially
       return std::is_sorted(std::move(__first), std::move(__last), std::forward<_Comp>(__comp));
     }
   }

>From 101830c975da5c252f0ccc7478540fe967807a43 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Wed, 21 Jan 2026 15:36:30 +0000
Subject: [PATCH 5/6] Added a missing include

---
 .../alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp      | 2 +-
 .../alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp
index a7708721c6035..5b1e5ce60c253 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted.pass.cpp
@@ -17,8 +17,8 @@
 #include <algorithm>
 #include <cassert>
 #include <functional>
-#include <numeric>
 #include <limits>
+#include <numeric>
 
 #include "test_execution_policies.h"
 #include "test_iterators.h"
diff --git a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp
index b7b01188d7f0b..8a6c24ca22af1 100644
--- a/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.sort/is.sorted/pstl.is_sorted_comp.pass.cpp
@@ -18,6 +18,7 @@
 #include <algorithm>
 #include <cassert>
 #include <functional>
+#include <limits>
 #include <numeric>
 
 #include "test_execution_policies.h"

>From c705c8009480eb329c2c2a37f80b69435ab0fa84 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Sat, 24 Jan 2026 10:17:37 +0000
Subject: [PATCH 6/6] Updated iterator changes with a more clear intent, added
 termination tests

---
 libcxx/include/__pstl/backends/default.h                  | 6 +++---
 .../test/std/algorithms/pstl.exception_handling.pass.cpp  | 8 ++++++++
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/libcxx/include/__pstl/backends/default.h b/libcxx/include/__pstl/backends/default.h
index 70ca0cec094c1..be90715af13b5 100644
--- a/libcxx/include/__pstl/backends/default.h
+++ b/libcxx/include/__pstl/backends/default.h
@@ -20,6 +20,7 @@
 #include <__functional/operations.h>
 #include <__iterator/concepts.h>
 #include <__iterator/iterator_traits.h>
+#include <__iterator/next.h>
 #include <__pstl/backend_fwd.h>
 #include <__pstl/dispatch.h>
 #include <__utility/empty.h>
@@ -398,11 +399,10 @@ struct __is_sorted<__default_backend_tag, _ExecutionPolicy> {
     if constexpr (__has_bidirectional_iterator_category<_ForwardIterator>::value) {
       if (__first == __last)
         return true; // Empty, sorted by definition
-      _ForwardIterator __first2 = __first;
-      ++__first2; // __first2 = __first + 1
+      _ForwardIterator __first2 = std::next(__first);
       if (__first2 == __last)
         return true; // Only one element, sorted by definition
-      --__last;      // __last = __last - 1
+      --__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()(
diff --git a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
index e24ce66314197..a3db159289393 100644
--- a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
+++ b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
@@ -282,6 +282,14 @@ int main(int, char**) {
         assert_non_throwing([=, &policy] {
           (void)std::stable_sort(policy, std::move(first1), std::move(last1), compare);
         });
+
+        // is_sorted(first, last)
+        assert_non_throwing([=, &policy] { (void)std::is_sorted(policy, std::move(first1), std::move(last1)); });
+
+        // is_sorted(first, last, comp)
+        assert_non_throwing([=, &policy] {
+          (void)std::is_sorted(policy, std::move(first1), std::move(last1), compare);
+        });
       }
 
       {



More information about the libcxx-commits mailing list