[libcxx-commits] [libcxx] [libc++][pstl] Add serial implementations of partition algorithms to PSTL (PR #234598)

Michael G. Kazakov via libcxx-commits libcxx-commits at lists.llvm.org
Fri Oct 23 01:59:43 PDT 2026


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

>From 09440a09170763bfb08987f6c5a8651637ce4d56 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Thu, 22 Oct 2026 13:16:58 +0100
Subject: [PATCH 1/3] Added a serial implementation of std::partition to PSTL

---
 libcxx/include/__algorithm/pstl.h             |  13 ++
 libcxx/include/__pstl/backend_fwd.h           |   6 +
 libcxx/include/__pstl/backends/serial.h       |  10 ++
 .../pstl.iterator-requirements.verify.cpp     |   4 +
 .../algorithms/pstl.nodiscard.verify.cpp      |   2 +
 .../alg.partitions/pstl.partition.pass.cpp    | 127 ++++++++++++++++++
 .../pstl.exception_handling.pass.cpp          |   3 +
 7 files changed, 165 insertions(+)
 create mode 100644 libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.partition.pass.cpp

diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index 66fa8b3cdd8a00..879cc7b2341f32 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -718,6 +718,19 @@ is_partitioned(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIt
       std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), std::move(__pred));
 }
 
+template <class _ExecutionPolicy,
+          class _ForwardIterator,
+          class _Predicate,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+[[nodiscard]] _ForwardIterator
+partition(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Predicate __pred) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "partition requires ForwardIterators");
+  using _Implementation = __pstl::__dispatch<__pstl::__partition, __pstl::__current_configuration, _RawPolicy>;
+  return __pstl::__handle_exception<_Implementation>(
+      std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), std::move(__pred));
+}
+
 template <class _ExecutionPolicy,
           class _ForwardIterator1,
           class _ForwardIterator2,
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index 8bb23f352c9235..836263cd0fde77 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -501,6 +501,12 @@ struct __uninitialized_fill_n;
 // optional<_ForwardIterator>
 // operator()(_Policy&& __policy, _ForwardIterator __first, _Size __n, const _Tp &__value) const noexcept;
 
+template <class _Backend, class _ExecutionPolicy>
+struct __partition;
+// template <class _Policy, class _ForwardIterator, class _Predicate>
+// optional<_ForwardIterator>
+// operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Predicate __pred) const noexcept;
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/include/__pstl/backends/serial.h b/libcxx/include/__pstl/backends/serial.h
index c06e4d31ff3cba..0bc75818ee80d3 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -18,6 +18,7 @@
 #include <__algorithm/min_element.h>
 #include <__algorithm/minmax_element.h>
 #include <__algorithm/mismatch.h>
+#include <__algorithm/partition.h>
 #include <__algorithm/reverse.h>
 #include <__algorithm/search.h>
 #include <__algorithm/search_n.h>
@@ -313,6 +314,15 @@ struct __uninitialized_move<__serial_backend_tag, _ExecutionPolicy> {
   }
 };
 
+template <class _ExecutionPolicy>
+struct __partition<__serial_backend_tag, _ExecutionPolicy> {
+  template <class _Policy, class _ForwardIterator, class _Predicate>
+  _LIBCPP_HIDE_FROM_ABI optional<_ForwardIterator>
+  operator()(_Policy&&, _ForwardIterator __first, _ForwardIterator __last, _Predicate __pred) const noexcept {
+    return std::partition(std::move(__first), std::move(__last), std::move(__pred));
+  }
+};
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
index d99869ce73f103..2ce326e28bfd5d 100644
--- a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
@@ -157,6 +157,10 @@ void f(non_forward_iterator non_fwd,
         pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: is_partitioned}}
   }
 
+  {
+    (void)std::partition(pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: partition}}
+  }
+
   {
     (void)std::is_heap(pol, non_random, non_random);       // expected-error@*:* {{static assertion failed: is_heap}}
     (void)std::is_heap(pol, non_random, non_random, pred); // expected-error@*:* {{static assertion failed: is_heap}}
diff --git a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
index f36f12a135fbaf..c64243f817d68d 100644
--- a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
@@ -115,4 +115,6 @@ void test() {
   std::search_n(std::execution::par, std::begin(a), std::end(a), 1, 1);
   // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::search_n(std::execution::par, std::begin(a), std::end(a), 1, 1, pred2);
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::partition(std::execution::par, std::begin(a), std::end(a), pred);
 }
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.partition.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.partition.pass.cpp
new file mode 100644
index 00000000000000..1a22cc00effea4
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.partition.pass.cpp
@@ -0,0 +1,127 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <algorithm>
+
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// template <class ExecutionPolicy,
+//           class ForwardIterator,
+//           class Predicate>
+//   ForwardIterator partition(ExecutionPolicy&& exec,
+//                             ForwardIterator first,
+//                             ForwardIterator last,
+//                             Predicate pred);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <numeric>
+#include <type_traits>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "type_algorithms.h"
+
+EXECUTION_POLICY_SFINAE_TEST(partition);
+
+static_assert(sfinae_test_partition<int, int*, int*, bool (*)(int, int)>);
+static_assert(!sfinae_test_partition<std::execution::parallel_policy, int*, int*, bool (*)(int, int)>);
+
+template <class Iter>
+struct Test {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    auto is_odd = [](int i) { return i % 2 == 1; };
+    { // Check the return type
+      int a[] = {0};
+      auto r  = std::partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      static_assert(std::is_same_v<decltype(r), Iter>);
+    }
+    { // Check mixed
+      int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+      auto r  = std::partition(Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::begin(a) + 5));
+      for (auto i = Iter(std::begin(a)); i != r; ++i)
+        assert(is_odd(*i));
+      for (auto i = r; i != Iter(std::end(a)); ++i)
+        assert(!is_odd(*i));
+    }
+    { // Check empty
+      int a[] = {0};
+      auto r  = std::partition(Iter(std::begin(a)), Iter(std::begin(a)), is_odd);
+      assert(r == Iter(std::begin(a)));
+    }
+    { // Check all false
+      int a[10] = {0};
+      std::generate(std::begin(a), std::end(a), [v = 0]() mutable { return 2 * v++; });
+      auto r = std::partition(Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::begin(a)));
+    }
+    { // Check all true
+      int a[10] = {0};
+      std::generate(std::begin(a), std::end(a), [v = 0]() mutable { return 1 + 2 * v++; });
+      auto r = std::partition(Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::end(a)));
+    }
+    { // Check all true but last
+      int a[10] = {0};
+      std::generate(std::begin(a), std::end(a), [v = 0]() mutable { return 1 + 2 * v++; });
+      a[std::size(a) - 1] = 10;
+      auto r              = std::partition(Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::end(a) - 1));
+      for (auto i = Iter(std::begin(a)); i != r; ++i)
+        assert(is_odd(*i));
+      for (auto i = r; i != Iter(std::end(a)); ++i)
+        assert(!is_odd(*i));
+    }
+    { // Check all true but first
+      int a[10] = {0};
+      std::generate(std::begin(a), std::end(a), [v = 0]() mutable { return 1 + 2 * v++; });
+      a[0]   = 10;
+      auto r = std::partition(Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::end(a) - 1));
+      for (auto i = Iter(std::begin(a)); i != r; ++i)
+        assert(is_odd(*i));
+      for (auto i = r; i != Iter(std::end(a)); ++i)
+        assert(!is_odd(*i));
+    }
+    { // Check all false but last
+      int a[10] = {0};
+      std::generate(std::begin(a), std::end(a), [v = 0]() mutable { return 2 * v++; });
+      a[std::size(a) - 1] = 11;
+      auto r              = std::partition(Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::begin(a) + 1));
+      for (auto i = Iter(std::begin(a)); i != r; ++i)
+        assert(is_odd(*i));
+      for (auto i = r; i != Iter(std::end(a)); ++i)
+        assert(!is_odd(*i));
+    }
+    { // Check all false but first
+      int a[10] = {0};
+      std::generate(std::begin(a), std::end(a), [v = 0]() mutable { return 2 * v++; });
+      a[0]   = 11;
+      auto r = std::partition(Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::begin(a) + 1));
+      for (auto i = Iter(std::begin(a)); i != r; ++i)
+        assert(is_odd(*i));
+      for (auto i = r; i != Iter(std::end(a)); ++i)
+        assert(!is_odd(*i));
+    }
+  }
+};
+
+int main(int, char**) {
+  types::for_each(types::forward_iterator_list<int*>{}, TestIteratorWithPolicies<Test>{});
+  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 64f7b396813055..44926f4461ab9c 100644
--- a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
+++ b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
@@ -286,6 +286,9 @@ int main(int, char**) {
         assert_non_throwing([=, &policy] {
           (void)std::is_partitioned(policy, std::move(first1), std::move(last1), pred);
         });
+
+        // partition(first, last, pred)
+        assert_non_throwing([=, &policy] { (void)std::partition(policy, std::move(first1), std::move(last1), pred); });
       }
 
       {

>From 3d448dcf1f2a09318958a3aebf84d1e27e40b693 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Thu, 22 Oct 2026 16:09:44 +0100
Subject: [PATCH 2/3] Added std::stable_partition

---
 libcxx/include/__algorithm/pstl.h             |  14 ++
 libcxx/include/__pstl/backend_fwd.h           |   7 +
 libcxx/include/__pstl/backends/serial.h       |  10 +
 .../pstl.iterator-requirements.verify.cpp     |   2 +
 .../algorithms/pstl.nodiscard.verify.cpp      |   2 +
 .../pstl.stable_partition.pass.cpp            | 180 ++++++++++++++++++
 .../pstl.exception_handling.pass.cpp          |   5 +
 7 files changed, 220 insertions(+)
 create mode 100644 libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.stable_partition.pass.cpp

diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index 879cc7b2341f32..8f11cfc0caf595 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -731,6 +731,20 @@ partition(_ExecutionPolicy&& __policy, _ForwardIterator __first, _ForwardIterato
       std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), std::move(__pred));
 }
 
+template <class _ExecutionPolicy,
+          class _BidirectionalIterator,
+          class _Predicate,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+[[nodiscard]] _BidirectionalIterator stable_partition(
+    _ExecutionPolicy&& __policy, _BidirectionalIterator __first, _BidirectionalIterator __last, _Predicate __pred) {
+  _LIBCPP_REQUIRE_CPP17_BIDIRECTIONAL_ITERATOR(
+      _BidirectionalIterator, "stable_partition requires BidirectionalIterators");
+  using _Implementation = __pstl::__dispatch<__pstl::__stable_partition, __pstl::__current_configuration, _RawPolicy>;
+  return __pstl::__handle_exception<_Implementation>(
+      std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), std::move(__pred));
+}
+
 template <class _ExecutionPolicy,
           class _ForwardIterator1,
           class _ForwardIterator2,
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index ae594a4bb2b1ff..9377c1a4da2ae8 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -392,6 +392,13 @@ struct __sort;
 // optional<__empty>
 // operator()(_Policy&&, _RandomAccessIterator __first, _RandomAccessIterator __last, _Comp __comp) const noexcept;
 
+template <class _Backend, class _ExecutionPolicy>
+struct __stable_partition;
+// template <class _Policy, class _BidirectionalIterator, class _Predicate>
+// optional<_BidirectionalIterator>
+// operator()(_Policy&& __policy, _BidirectionalIterator __first, _BidirectionalIterator __last, _Predicate __pred)
+// const noexcept;
+
 template <class _Backend, class _ExecutionPolicy>
 struct __stable_sort;
 // template <class _Policy, class _RandomAccessIterator, class _Comp>
diff --git a/libcxx/include/__pstl/backends/serial.h b/libcxx/include/__pstl/backends/serial.h
index 0bc75818ee80d3..7893cf60a4a6f6 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -22,6 +22,7 @@
 #include <__algorithm/reverse.h>
 #include <__algorithm/search.h>
 #include <__algorithm/search_n.h>
+#include <__algorithm/stable_partition.h>
 #include <__algorithm/stable_sort.h>
 #include <__algorithm/swap_ranges.h>
 #include <__algorithm/transform.h>
@@ -323,6 +324,15 @@ struct __partition<__serial_backend_tag, _ExecutionPolicy> {
   }
 };
 
+template <class _ExecutionPolicy>
+struct __stable_partition<__serial_backend_tag, _ExecutionPolicy> {
+  template <class _Policy, class _BidirectionalIterator, class _Predicate>
+  optional<_BidirectionalIterator> operator()(
+      _Policy&&, _BidirectionalIterator __first, _BidirectionalIterator __last, _Predicate __pred) const noexcept {
+    return std::stable_partition(std::move(__first), std::move(__last), std::move(__pred));
+  }
+};
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
index 2ce326e28bfd5d..b2cd916a1d512f 100644
--- a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
@@ -159,6 +159,8 @@ void f(non_forward_iterator non_fwd,
 
   {
     (void)std::partition(pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: partition}}
+    (void)std::stable_partition(
+        pol, non_bidir, non_bidir, pred); // expected-error@*:* {{static assertion failed: stable_partition}}
   }
 
   {
diff --git a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
index c64243f817d68d..90edc4ee2256ac 100644
--- a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
@@ -117,4 +117,6 @@ void test() {
   std::search_n(std::execution::par, std::begin(a), std::end(a), 1, 1, pred2);
   // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::partition(std::execution::par, std::begin(a), std::end(a), pred);
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::stable_partition(std::execution::par, std::begin(a), std::end(a), pred);
 }
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.stable_partition.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.stable_partition.pass.cpp
new file mode 100644
index 00000000000000..dbb5c65bb4ed5f
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.stable_partition.pass.cpp
@@ -0,0 +1,180 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <algorithm>
+
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// template <class ExecutionPolicy,
+//           class BidirectionalIterator,
+//           class Predicate>
+//   BidirectionalIterator stable_partition(ExecutionPolicy&& exec,
+//                                          BidirectionalIterator first,
+//                                          BidirectionalIterator last,
+//                                          Predicate pred);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <numeric>
+#include <type_traits>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "type_algorithms.h"
+
+EXECUTION_POLICY_SFINAE_TEST(stable_partition);
+
+static_assert(sfinae_test_stable_partition<int, int*, int*, bool (*)(int, int)>);
+static_assert(!sfinae_test_stable_partition<std::execution::parallel_policy, int*, int*, bool (*)(int, int)>);
+
+template <class Iter>
+struct Test {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    using P     = std::pair<int, int>;
+    auto is_odd = [](const P& p) { return p.first % 2 == 1; };
+    { // Check the return type
+      P a[]  = {P(0, 1)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      static_assert(std::is_same_v<decltype(r), Iter>);
+    }
+    { // Check mixed
+      P a[]  = {P(0, 1), P(0, 2), P(1, 1), P(1, 2), P(2, 1), P(2, 2), P(3, 1), P(3, 2), P(4, 1), P(4, 2)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::begin(a) + 4));
+      assert(a[0] == P(1, 1));
+      assert(a[1] == P(1, 2));
+      assert(a[2] == P(3, 1));
+      assert(a[3] == P(3, 2));
+      assert(a[4] == P(0, 1));
+      assert(a[5] == P(0, 2));
+      assert(a[6] == P(2, 1));
+      assert(a[7] == P(2, 2));
+      assert(a[8] == P(4, 1));
+      assert(a[9] == P(4, 2));
+    }
+    { // Check empty
+      P a[]  = {P(0, 1)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::begin(a)), is_odd);
+      assert(r == Iter(std::begin(a)));
+    }
+    { // Check one true
+      P a[]  = {P(1, 1)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::end(a)));
+      assert(a[0] == P(1, 1));
+    }
+    { // Check one false
+      P a[]  = {P(0, 1)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::begin(a)));
+      assert(a[0] == P(0, 1));
+    }
+    { // Check all false
+      P a[]  = {P(0, 1), P(0, 2), P(2, 1), P(2, 2), P(4, 1), P(4, 2), P(6, 1), P(6, 2), P(8, 1), P(8, 2)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::begin(a)));
+      assert(a[0] == P(0, 1));
+      assert(a[1] == P(0, 2));
+      assert(a[2] == P(2, 1));
+      assert(a[3] == P(2, 2));
+      assert(a[4] == P(4, 1));
+      assert(a[5] == P(4, 2));
+      assert(a[6] == P(6, 1));
+      assert(a[7] == P(6, 2));
+      assert(a[8] == P(8, 1));
+      assert(a[9] == P(8, 2));
+    }
+    { // Check all true
+      P a[]  = {P(1, 1), P(1, 2), P(3, 1), P(3, 2), P(5, 1), P(5, 2), P(7, 1), P(7, 2), P(9, 1), P(9, 2)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::end(a)));
+      assert(a[0] == P(1, 1));
+      assert(a[1] == P(1, 2));
+      assert(a[2] == P(3, 1));
+      assert(a[3] == P(3, 2));
+      assert(a[4] == P(5, 1));
+      assert(a[5] == P(5, 2));
+      assert(a[6] == P(7, 1));
+      assert(a[7] == P(7, 2));
+      assert(a[8] == P(9, 1));
+      assert(a[9] == P(9, 2));
+    }
+    { // Check all false but first true
+      P a[]  = {P(1, 1), P(0, 2), P(2, 1), P(2, 2), P(4, 1), P(4, 2), P(6, 1), P(6, 2), P(8, 1), P(8, 2)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::begin(a) + 1));
+      assert(a[0] == P(1, 1));
+      assert(a[1] == P(0, 2));
+      assert(a[2] == P(2, 1));
+      assert(a[3] == P(2, 2));
+      assert(a[4] == P(4, 1));
+      assert(a[5] == P(4, 2));
+      assert(a[6] == P(6, 1));
+      assert(a[7] == P(6, 2));
+      assert(a[8] == P(8, 1));
+      assert(a[9] == P(8, 2));
+    }
+    { // Check all false but last true
+      P a[]  = {P(0, 1), P(0, 2), P(2, 1), P(2, 2), P(4, 1), P(4, 2), P(6, 1), P(6, 2), P(8, 1), P(1, 2)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::begin(a) + 1));
+      assert(a[0] == P(1, 2));
+      assert(a[1] == P(0, 1));
+      assert(a[2] == P(0, 2));
+      assert(a[3] == P(2, 1));
+      assert(a[4] == P(2, 2));
+      assert(a[5] == P(4, 1));
+      assert(a[6] == P(4, 2));
+      assert(a[7] == P(6, 1));
+      assert(a[8] == P(6, 2));
+      assert(a[9] == P(8, 1));
+    }
+    { // Check all true but first false
+      P a[]  = {P(0, 1), P(1, 2), P(3, 1), P(3, 2), P(5, 1), P(5, 2), P(7, 1), P(7, 2), P(9, 1), P(9, 2)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::end(a) - 1));
+      assert(a[0] == P(1, 2));
+      assert(a[1] == P(3, 1));
+      assert(a[2] == P(3, 2));
+      assert(a[3] == P(5, 1));
+      assert(a[4] == P(5, 2));
+      assert(a[5] == P(7, 1));
+      assert(a[6] == P(7, 2));
+      assert(a[7] == P(9, 1));
+      assert(a[8] == P(9, 2));
+      assert(a[9] == P(0, 1));
+    }
+    { // Check all true but last false
+      P a[]  = {P(1, 1), P(1, 2), P(3, 1), P(3, 2), P(5, 1), P(5, 2), P(7, 1), P(7, 2), P(9, 1), P(0, 2)};
+      auto r = std::stable_partition(policy, Iter(std::begin(a)), Iter(std::end(a)), is_odd);
+      assert(r == Iter(std::end(a) - 1));
+      assert(a[0] == P(1, 1));
+      assert(a[1] == P(1, 2));
+      assert(a[2] == P(3, 1));
+      assert(a[3] == P(3, 2));
+      assert(a[4] == P(5, 1));
+      assert(a[5] == P(5, 2));
+      assert(a[6] == P(7, 1));
+      assert(a[7] == P(7, 2));
+      assert(a[8] == P(9, 1));
+      assert(a[9] == P(0, 2));
+    }
+  }
+};
+
+int main(int, char**) {
+  types::for_each(types::bidirectional_iterator_list<std::pair<int, int>*>{}, TestIteratorWithPolicies<Test>{});
+  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 44926f4461ab9c..00e63d677a87bf 100644
--- a/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
+++ b/libcxx/test/std/algorithms/pstl.exception_handling.pass.cpp
@@ -289,6 +289,11 @@ int main(int, char**) {
 
         // partition(first, last, pred)
         assert_non_throwing([=, &policy] { (void)std::partition(policy, std::move(first1), std::move(last1), pred); });
+
+        // stable_partition(first, last, pred)
+        assert_non_throwing([=, &policy] {
+          (void)std::stable_partition(policy, std::move(first1), std::move(last1), pred);
+        });
       }
 
       {

>From c33a98de06606dc0104bd946c08db909a9e36d06 Mon Sep 17 00:00:00 2001
From: Michael Kazakov <mike.kazakov at gmail.com>
Date: Thu, 22 Oct 2026 17:13:28 +0100
Subject: [PATCH 3/3] Added std::partition_copy

---
 libcxx/include/__algorithm/pstl.h             | 29 ++++++
 libcxx/include/__pstl/backend_fwd.h           |  9 ++
 libcxx/include/__pstl/backends/serial.h       | 20 +++++
 .../pstl.iterator-requirements.verify.cpp     |  6 ++
 .../algorithms/pstl.nodiscard.verify.cpp      |  3 +
 .../pstl.partition_copy.pass.cpp              | 89 +++++++++++++++++++
 6 files changed, 156 insertions(+)
 create mode 100644 libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.partition_copy.pass.cpp

diff --git a/libcxx/include/__algorithm/pstl.h b/libcxx/include/__algorithm/pstl.h
index 8f11cfc0caf595..93363b5893f13b 100644
--- a/libcxx/include/__algorithm/pstl.h
+++ b/libcxx/include/__algorithm/pstl.h
@@ -745,6 +745,35 @@ template <class _ExecutionPolicy,
       std::forward<_ExecutionPolicy>(__policy), std::move(__first), std::move(__last), std::move(__pred));
 }
 
+template <class _ExecutionPolicy,
+          class _ForwardIterator,
+          class _ForwardOutIterator1,
+          class _ForwardOutIterator2,
+          class _Predicate,
+          class _RawPolicy                                    = __remove_cvref_t<_ExecutionPolicy>,
+          enable_if_t<is_execution_policy_v<_RawPolicy>, int> = 0>
+[[nodiscard]] pair<_ForwardOutIterator1, _ForwardOutIterator2> partition_copy(
+    _ExecutionPolicy&& __policy,
+    _ForwardIterator __first,
+    _ForwardIterator __last,
+    _ForwardOutIterator1 __out_true,
+    _ForwardOutIterator2 __out_false,
+    _Predicate __pred) {
+  _LIBCPP_REQUIRE_CPP17_FORWARD_ITERATOR(_ForwardIterator, "partition_copy requires ForwardIterators");
+  _LIBCPP_REQUIRE_CPP17_OUTPUT_ITERATOR(
+      _ForwardOutIterator1, decltype(*__first), "partition_copy requires an OutputIterator");
+  _LIBCPP_REQUIRE_CPP17_OUTPUT_ITERATOR(
+      _ForwardOutIterator2, decltype(*__first), "partition_copy requires an OutputIterator");
+  using _Implementation = __pstl::__dispatch<__pstl::__partition_copy, __pstl::__current_configuration, _RawPolicy>;
+  return __pstl::__handle_exception<_Implementation>(
+      std::forward<_ExecutionPolicy>(__policy),
+      std::move(__first),
+      std::move(__last),
+      std::move(__out_true),
+      std::move(__out_false),
+      std::move(__pred));
+}
+
 template <class _ExecutionPolicy,
           class _ForwardIterator1,
           class _ForwardIterator2,
diff --git a/libcxx/include/__pstl/backend_fwd.h b/libcxx/include/__pstl/backend_fwd.h
index 9377c1a4da2ae8..9b658ca1477728 100644
--- a/libcxx/include/__pstl/backend_fwd.h
+++ b/libcxx/include/__pstl/backend_fwd.h
@@ -313,6 +313,15 @@ struct __partition;
 // optional<_ForwardIterator>
 // operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last, _Predicate __pred) const noexcept;
 
+template <class _Backend, class _ExecutionPolicy>
+struct __partition_copy;
+// template <class _Policy, class _ForwardIterator,
+//           class _ForwardOutIterator1, class _ForwardOutIterator2, class _Predicate>
+// optional<pair<_ForwardOutIterator1, _ForwardOutIterator2>>
+// operator()(_Policy&& __policy, _ForwardIterator __first, _ForwardIterator __last,
+//                                _ForwardOutIterator1 __out_true, _ForwardOutIterator2 __out_false,
+//                                _Predicate __pred) const noexcept;
+
 template <class _Backend, class _ExecutionPolicy>
 struct __reduce;
 // template <class _Policy, class _ForwardIterator, class _Tp, class _BinaryOperation>
diff --git a/libcxx/include/__pstl/backends/serial.h b/libcxx/include/__pstl/backends/serial.h
index 7893cf60a4a6f6..3f1a27aab4b824 100644
--- a/libcxx/include/__pstl/backends/serial.h
+++ b/libcxx/include/__pstl/backends/serial.h
@@ -19,6 +19,7 @@
 #include <__algorithm/minmax_element.h>
 #include <__algorithm/mismatch.h>
 #include <__algorithm/partition.h>
+#include <__algorithm/partition_copy.h>
 #include <__algorithm/reverse.h>
 #include <__algorithm/search.h>
 #include <__algorithm/search_n.h>
@@ -333,6 +334,25 @@ struct __stable_partition<__serial_backend_tag, _ExecutionPolicy> {
   }
 };
 
+template <class _ExecutionPolicy>
+struct __partition_copy<__serial_backend_tag, _ExecutionPolicy> {
+  template <class _Policy,
+            class _ForwardIterator,
+            class _ForwardOutIterator1,
+            class _ForwardOutIterator2,
+            class _Predicate>
+  optional<pair<_ForwardOutIterator1, _ForwardOutIterator2>>
+  operator()(_Policy&&,
+             _ForwardIterator __first,
+             _ForwardIterator __last,
+             _ForwardOutIterator1 __out_true,
+             _ForwardOutIterator2 __out_false,
+             _Predicate __pred) const noexcept {
+    return std::partition_copy(
+        std::move(__first), std::move(__last), std::move(__out_true), std::move(__out_false), std::move(__pred));
+  }
+};
+
 } // namespace __pstl
 _LIBCPP_END_NAMESPACE_STD
 
diff --git a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
index b2cd916a1d512f..184577b2bf9d7a 100644
--- a/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.iterator-requirements.verify.cpp
@@ -161,6 +161,12 @@ void f(non_forward_iterator non_fwd,
     (void)std::partition(pol, non_fwd, non_fwd, pred); // expected-error@*:* {{static assertion failed: partition}}
     (void)std::stable_partition(
         pol, non_bidir, non_bidir, pred); // expected-error@*:* {{static assertion failed: stable_partition}}
+    (void)std::partition_copy(
+        pol, non_fwd, non_fwd, it, it, pred); // expected-error@*:* {{static assertion failed: partition_copy}}
+    (void)std::partition_copy(
+        pol, it, it, non_output, it, pred); // expected-error@*:* {{static assertion failed: partition_copy}}
+    (void)std::partition_copy(
+        pol, it, it, it, non_output, pred); // expected-error@*:* {{static assertion failed: partition_copy}}
   }
 
   {
diff --git a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
index 90edc4ee2256ac..8e38fa76141a33 100644
--- a/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/algorithms/pstl.nodiscard.verify.cpp
@@ -20,6 +20,7 @@
 void test() {
   int a[]    = {1};
   int b[]    = {1};
+  int c[]    = {1};
   auto pred  = [](auto) { return false; };
   auto pred2 = [](auto, auto) { return false; };
 
@@ -119,4 +120,6 @@ void test() {
   std::partition(std::execution::par, std::begin(a), std::end(a), pred);
   // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
   std::stable_partition(std::execution::par, std::begin(a), std::end(a), pred);
+  // expected-warning at +1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::partition_copy(std::execution::par, std::begin(a), std::end(a), std::begin(b), std::begin(c), pred);
 }
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.partition_copy.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.partition_copy.pass.cpp
new file mode 100644
index 00000000000000..91721e815c5f86
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/pstl.partition_copy.pass.cpp
@@ -0,0 +1,89 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <algorithm>
+
+// REQUIRES: std-at-least-c++17
+
+// UNSUPPORTED: libcpp-has-no-incomplete-pstl
+
+// template <class ExecutionPolicy,
+//           class ForwardIterator,
+//           class ForwardOutIterator1,
+//           class ForwardOutIterator2,
+//           class Predicate>
+//   pair<ForwardOutIterator1, ForwardOutIterator2>
+//   partition_copy(ExecutionPolicy&& exec,
+//                  ForwardIterator first,
+//                  ForwardIterator last,
+//                  ForwardOutIterator1 out_true,
+//                  ForwardOutIterator2 out_false,
+//                  Predicate pred);
+
+#include <algorithm>
+#include <cassert>
+#include <functional>
+#include <iterator>
+#include <limits>
+#include <numeric>
+#include <type_traits>
+
+#include "test_execution_policies.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "type_algorithms.h"
+
+EXECUTION_POLICY_SFINAE_TEST(partition_copy);
+
+static_assert(sfinae_test_partition_copy<int, int*, int*, int*, int*, bool (*)(int, int)>);
+static_assert(!sfinae_test_partition_copy<std::execution::parallel_policy, int*, int*, int*, int*, bool (*)(int, int)>);
+
+template <class Iter1, class Iter2>
+struct Test {
+  template <class ExecutionPolicy>
+  void operator()(ExecutionPolicy&& policy) {
+    {
+      auto is_odd = [](int i) { return i % 2 == 1; };
+      { // Check the return type
+        int a[]             = {0};
+        int t[std::size(a)] = {0};
+        int f[std::size(a)] = {0};
+        auto r              = std::partition_copy(
+            policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(t)), Iter2(std::begin(f)), is_odd);
+        static_assert(std::is_same_v<decltype(r), std::pair<Iter2, Iter2>>);
+      }
+      { // Equal sizes
+        int a[]             = {1, 2, 3, 4, 6, 8, 5, 7};
+        int t[std::size(a)] = {0};
+        int f[std::size(a)] = {0};
+        auto r              = std::partition_copy(
+            policy, Iter1(std::begin(a)), Iter1(std::end(a)), Iter2(std::begin(t)), Iter2(std::begin(f)), is_odd);
+        assert(r.first == Iter2(std::begin(t) + 4));
+        assert(t[0] == 1);
+        assert(t[1] == 3);
+        assert(t[2] == 5);
+        assert(t[3] == 7);
+        assert(r.second == Iter2(std::begin(f) + 4));
+        assert(f[0] == 2);
+        assert(f[1] == 4);
+        assert(f[2] == 6);
+        assert(f[3] == 8);
+      }
+    }
+  }
+};
+
+int main(int, char**) {
+  types::for_each(types::forward_iterator_list<const int*>{}, types::apply_type_identity{[](auto v) {
+                    using Iter = typename decltype(v)::type;
+                    types::for_each(
+                        types::forward_iterator_list<int*>{},
+                        TestIteratorWithPolicies<types::partial_instantiation<Test, Iter>::template apply>{});
+                  }});
+  return 0;
+}



More information about the libcxx-commits mailing list