[libcxx-commits] [libcxx] 8ed702b - [libc++][ranges] Implement `ranges::{, stable_}partition`.

Konstantin Varlamov via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jul 18 21:06:58 PDT 2022


Author: Konstantin Varlamov
Date: 2022-07-18T21:06:17-07:00
New Revision: 8ed702b83f20d0ec117264a8e7776098ae956250

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

LOG: [libc++][ranges] Implement `ranges::{,stable_}partition`.

Differential Revision: https://reviews.llvm.org/D129624

Added: 
    libcxx/include/__algorithm/ranges_iterator_concept.h

Modified: 
    libcxx/docs/Status/RangesAlgorithms.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__algorithm/iterator_operations.h
    libcxx/include/__algorithm/make_projected.h
    libcxx/include/__algorithm/partition.h
    libcxx/include/__algorithm/ranges_find_end.h
    libcxx/include/__algorithm/ranges_partition.h
    libcxx/include/__algorithm/ranges_stable_partition.h
    libcxx/include/__algorithm/rotate.h
    libcxx/include/__algorithm/stable_partition.h
    libcxx/include/algorithm
    libcxx/include/module.modulemap.in
    libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp
    libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp
    libcxx/test/libcxx/private_headers.verify.cpp
    libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_partition.pass.cpp
    libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_stable_partition.pass.cpp
    libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.compile.pass.cpp
    libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.compile.pass.cpp
    libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp
    libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/RangesAlgorithms.csv b/libcxx/docs/Status/RangesAlgorithms.csv
index 7a73504e4ba9f..3376b6a8522df 100644
--- a/libcxx/docs/Status/RangesAlgorithms.csv
+++ b/libcxx/docs/Status/RangesAlgorithms.csv
@@ -10,7 +10,7 @@ Search,adjacent_find,Nikolas Klauser,`D126610 <https://llvm.org/D126610>`_,✅
 Search,mismatch,Nikolas Klauser,`D117817 <https://llvm.org/D117817>`_,✅
 Search,equal,Nikolas Klauser,`D123681 <https://llvm.org/D123681>`_,✅
 Search,lexicographical_compare,Nikolas Klauser,`D127130 <https://llvm.org/D127130>`_,✅
-Search,partition_point,Christopher Di Bella,`D105794 <https://llvm.org/D105794>`_,Under review
+Search,partition_point,Konstantin Varlamov,n/a,In progress
 Search,lower_bound,Nikolas Klauser,`D121964 <https://llvm.org/D121964>`_,✅
 Search,upper_bound,Nikolas Klauser,`D121964 <https://llvm.org/D121964>`_,✅
 Search,equal_range,Christopher Di Bella,n/a,Not started
@@ -58,7 +58,7 @@ Write,reverse_copy,Nikolas Klauser,`D127211 <https://llvm.org/D127211>`_,✅
 Write,rotate_copy,Nikolas Klauser,`D127211 <https://llvm.org/D127211>`_,✅
 Write,sample,Not assigned,n/a,Not started
 Write,unique_copy,Not assigned,n/a,Not started
-Write,partition_copy,Not assigned,n/a,Not started
+Write,partition_copy,Konstantin Varlamov,n/a,In progress
 Write,partial_sort_copy,Not assigned,n/a,Not started
 Merge,merge,Hui Xie,`D128611 <https://llvm.org/D128611>`_,✅
 Merge,set_
diff erence,Hui Xie,`D128983 <https://llvm.org/D128983>`_,✅
@@ -71,8 +71,8 @@ Permutation,reverse,Nikolas Klauser,`D125752 <https://llvm.org/D125752>`_,✅
 Permutation,rotate,Nikolas Klauser,`D124122 <https://llvm.org/D124122>`_,Under review
 Permutation,shuffle,Not assigned,n/a,Not started
 Permutation,unique,Not assigned,n/a,Not started
-Permutation,partition,Not assigned,n/a,Not started
-Permutation,stable_partition,Not assigned,n/a,Not started
+Permutation,partition,Konstantin Varlamov,`D129624 <https://llvm.org/D129624>`_,✅
+Permutation,stable_partition,Konstantin Varlamov,`D129624 <https://llvm.org/D129624>`_,✅
 Permutation,sort,Konstantin Varlamov,`D127557 <https://llvm.org/D127557>`_,✅
 Permutation,stable_sort,Konstantin Varlamov,`D127834 <https://llvm.org/D127834>`_,✅
 Permutation,partial_sort,Konstantin Varlamov,n/a,In progress

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index e69b0516b372f..0193f6162cf72 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -98,6 +98,7 @@ set(files
   __algorithm/ranges_is_partitioned.h
   __algorithm/ranges_is_sorted.h
   __algorithm/ranges_is_sorted_until.h
+  __algorithm/ranges_iterator_concept.h
   __algorithm/ranges_lexicographical_compare.h
   __algorithm/ranges_lower_bound.h
   __algorithm/ranges_make_heap.h

diff  --git a/libcxx/include/__algorithm/iterator_operations.h b/libcxx/include/__algorithm/iterator_operations.h
index de9c8a130ecb8..03dd2c41e94af 100644
--- a/libcxx/include/__algorithm/iterator_operations.h
+++ b/libcxx/include/__algorithm/iterator_operations.h
@@ -85,6 +85,13 @@ struct _IterOps<_ClassicAlgPolicy> {
     return __last;
   }
 
+  template <class _Iter>
+  _LIBCPP_HIDE_FROM_ABI static _LIBCPP_CONSTEXPR_AFTER_CXX11
+  __uncvref_t<_Iter> next(_Iter&& __it,
+                          typename iterator_traits<__uncvref_t<_Iter> >::
diff erence_type __n = 1){
+    return std::next(std::forward<_Iter>(__it), __n);
+  }
+
   template <class _Iter>
   _LIBCPP_HIDE_FROM_ABI static _LIBCPP_CONSTEXPR_AFTER_CXX11
   void __advance_to(_Iter& __first, _Iter __last) {

diff  --git a/libcxx/include/__algorithm/make_projected.h b/libcxx/include/__algorithm/make_projected.h
index 6d8ebfd3d90e8..64fc3dfb6a12c 100644
--- a/libcxx/include/__algorithm/make_projected.h
+++ b/libcxx/include/__algorithm/make_projected.h
@@ -27,6 +27,21 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 namespace ranges {
 
+template <class _Pred, class _Proj>
+_LIBCPP_HIDE_FROM_ABI constexpr static
+decltype(auto) __make_projected_pred(_Pred& __pred, _Proj& __proj) {
+  if constexpr (same_as<decay_t<_Proj>, identity> && !is_member_pointer_v<decay_t<_Pred>>) {
+    // Avoid creating the lambda and just use the pristine predicate -- for certain algorithms, this would enable
+    // optimizations that rely on the type of the predicate.
+    return __pred;
+
+  } else {
+    return [&](auto&& __x) {
+      return std::invoke(__pred, std::invoke(__proj, std::forward<decltype(__x)>(__x)));
+    };
+  }
+}
+
 template <class _Comp, class _Proj>
 _LIBCPP_HIDE_FROM_ABI constexpr static
 decltype(auto) __make_projected_comp(_Comp& __comp, _Proj& __proj) {

diff  --git a/libcxx/include/__algorithm/partition.h b/libcxx/include/__algorithm/partition.h
index 73d94831ed876..60b4e290ebebc 100644
--- a/libcxx/include/__algorithm/partition.h
+++ b/libcxx/include/__algorithm/partition.h
@@ -9,9 +9,12 @@
 #ifndef _LIBCPP___ALGORITHM_PARTITION_H
 #define _LIBCPP___ALGORITHM_PARTITION_H
 
+#include <__algorithm/iterator_operations.h>
 #include <__config>
 #include <__iterator/iterator_traits.h>
-#include <__utility/swap.h>
+#include <__utility/move.h>
+#include <__utility/pair.h>
+#include <type_traits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -19,40 +22,45 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _Predicate, class _ForwardIterator>
-_LIBCPP_CONSTEXPR_AFTER_CXX17 _ForwardIterator
-__partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate __pred, forward_iterator_tag)
+template <class _Predicate, class _AlgPolicy, class _ForwardIterator, class _Sentinel>
+_LIBCPP_CONSTEXPR_AFTER_CXX17 pair<_ForwardIterator, _ForwardIterator>
+__partition_impl(_ForwardIterator __first, _Sentinel __last, _Predicate __pred, forward_iterator_tag)
 {
     while (true)
     {
         if (__first == __last)
-            return __first;
+            return std::make_pair(std::move(__first), std::move(__first));
         if (!__pred(*__first))
             break;
         ++__first;
     }
-    for (_ForwardIterator __p = __first; ++__p != __last;)
+
+    _ForwardIterator __p = __first;
+    while (++__p != __last)
     {
         if (__pred(*__p))
         {
-            swap(*__first, *__p);
+            _IterOps<_AlgPolicy>::iter_swap(__first, __p);
             ++__first;
         }
     }
-    return __first;
+    return std::make_pair(std::move(__first), std::move(__p));
 }
 
-template <class _Predicate, class _BidirectionalIterator>
-_LIBCPP_CONSTEXPR_AFTER_CXX17 _BidirectionalIterator
-__partition(_BidirectionalIterator __first, _BidirectionalIterator __last, _Predicate __pred,
+template <class _Predicate, class _AlgPolicy, class _BidirectionalIterator, class _Sentinel>
+_LIBCPP_CONSTEXPR_AFTER_CXX17 pair<_BidirectionalIterator, _BidirectionalIterator>
+__partition_impl(_BidirectionalIterator __first, _Sentinel __sentinel, _Predicate __pred,
             bidirectional_iterator_tag)
 {
+    _BidirectionalIterator __original_last = _IterOps<_AlgPolicy>::next(__first, __sentinel);
+    _BidirectionalIterator __last = __original_last;
+
     while (true)
     {
         while (true)
         {
             if (__first == __last)
-                return __first;
+                return std::make_pair(std::move(__first), std::move(__original_last));
             if (!__pred(*__first))
                 break;
             ++__first;
@@ -60,20 +68,29 @@ __partition(_BidirectionalIterator __first, _BidirectionalIterator __last, _Pred
         do
         {
             if (__first == --__last)
-                return __first;
+                return std::make_pair(std::move(__first), std::move(__original_last));
         } while (!__pred(*__last));
-        swap(*__first, *__last);
+        _IterOps<_AlgPolicy>::iter_swap(__first, __last);
         ++__first;
     }
 }
 
+template <class _AlgPolicy, class _ForwardIterator, class _Sentinel, class _Predicate, class _IterCategory>
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX17
+pair<_ForwardIterator, _ForwardIterator> __partition(
+    _ForwardIterator __first, _Sentinel __last, _Predicate&& __pred, _IterCategory __iter_category) {
+  return std::__partition_impl<__uncvref_t<_Predicate>&, _AlgPolicy>(
+      std::move(__first), std::move(__last), __pred, __iter_category);
+}
+
 template <class _ForwardIterator, class _Predicate>
 inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
 _ForwardIterator
 partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate __pred)
 {
-    return _VSTD::__partition<_Predicate&>(
-        __first, __last, __pred, typename iterator_traits<_ForwardIterator>::iterator_category());
+  using _IterCategory = typename iterator_traits<_ForwardIterator>::iterator_category;
+  auto __result = std::__partition<_ClassicAlgPolicy>(std::move(__first), std::move(__last), __pred, _IterCategory());
+  return __result.first;
 }
 
 _LIBCPP_END_NAMESPACE_STD

diff  --git a/libcxx/include/__algorithm/ranges_find_end.h b/libcxx/include/__algorithm/ranges_find_end.h
index fec709e79f5ab..270b006498486 100644
--- a/libcxx/include/__algorithm/ranges_find_end.h
+++ b/libcxx/include/__algorithm/ranges_find_end.h
@@ -11,6 +11,7 @@
 
 #include <__algorithm/find_end.h>
 #include <__algorithm/iterator_operations.h>
+#include <__algorithm/ranges_iterator_concept.h>
 #include <__config>
 #include <__functional/identity.h>
 #include <__functional/ranges_operations.h>
@@ -29,23 +30,6 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _Iter>
-consteval auto __get_iterator_concept() {
-  if constexpr (contiguous_iterator<_Iter>)
-    return contiguous_iterator_tag();
-  else if constexpr (random_access_iterator<_Iter>)
-    return random_access_iterator_tag();
-  else if constexpr (bidirectional_iterator<_Iter>)
-    return bidirectional_iterator_tag();
-  else if constexpr (forward_iterator<_Iter>)
-    return forward_iterator_tag();
-  else if constexpr (input_iterator<_Iter>)
-    return input_iterator_tag();
-}
-
-template <class _Iter>
-using __iterator_concept = decltype(__get_iterator_concept<_Iter>());
-
 namespace ranges {
 namespace __find_end {
 struct __fn {

diff  --git a/libcxx/include/__algorithm/ranges_iterator_concept.h b/libcxx/include/__algorithm/ranges_iterator_concept.h
new file mode 100644
index 0000000000000..3323119317aec
--- /dev/null
+++ b/libcxx/include/__algorithm/ranges_iterator_concept.h
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___ALGORITHM_RANGES_ITERATOR_CONCEPT_H
+#define _LIBCPP___ALGORITHM_RANGES_ITERATOR_CONCEPT_H
+
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/iterator_traits.h>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace ranges {
+
+template <class _IterMaybeQualified>
+consteval auto __get_iterator_concept() {
+  using _Iter = __uncvref_t<_IterMaybeQualified>;
+
+  if constexpr (contiguous_iterator<_Iter>)
+    return contiguous_iterator_tag();
+  else if constexpr (random_access_iterator<_Iter>)
+    return random_access_iterator_tag();
+  else if constexpr (bidirectional_iterator<_Iter>)
+    return bidirectional_iterator_tag();
+  else if constexpr (forward_iterator<_Iter>)
+    return forward_iterator_tag();
+  else if constexpr (input_iterator<_Iter>)
+    return input_iterator_tag();
+}
+
+template <class _Iter>
+using __iterator_concept = decltype(__get_iterator_concept<_Iter>());
+
+} // namespace ranges
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+#endif // _LIBCPP___ALGORITHM_RANGES_ITERATOR_CONCEPT_H

diff  --git a/libcxx/include/__algorithm/ranges_partition.h b/libcxx/include/__algorithm/ranges_partition.h
index c145e7bdb4a23..60bee699d90e2 100644
--- a/libcxx/include/__algorithm/ranges_partition.h
+++ b/libcxx/include/__algorithm/ranges_partition.h
@@ -9,8 +9,10 @@
 #ifndef _LIBCPP___ALGORITHM_RANGES_PARTITION_H
 #define _LIBCPP___ALGORITHM_RANGES_PARTITION_H
 
+#include <__algorithm/iterator_operations.h>
 #include <__algorithm/make_projected.h>
 #include <__algorithm/partition.h>
+#include <__algorithm/ranges_iterator_concept.h>
 #include <__config>
 #include <__functional/identity.h>
 #include <__functional/invoke.h>
@@ -21,10 +23,10 @@
 #include <__iterator/projected.h>
 #include <__ranges/access.h>
 #include <__ranges/concepts.h>
-#include <__ranges/dangling.h>
 #include <__ranges/subrange.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
+#include <type_traits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -39,13 +41,21 @@ namespace __partition {
 
 struct __fn {
 
+  template <class _Iter, class _Sent, class _Proj, class _Pred>
+  _LIBCPP_HIDE_FROM_ABI static constexpr
+  subrange<__uncvref_t<_Iter>> __partition_fn_impl(_Iter&& __first, _Sent&& __last, _Pred&& __pred, _Proj&& __proj) {
+    auto&& __projected_pred = ranges::__make_projected_pred(__pred, __proj);
+    auto __result = std::__partition<_RangeAlgPolicy>(
+        std::move(__first), std::move(__last), __projected_pred, __iterator_concept<_Iter>());
+
+    return {std::move(__result.first), std::move(__result.second)};
+  }
+
   template <permutable _Iter, sentinel_for<_Iter> _Sent, class _Proj = identity,
             indirect_unary_predicate<projected<_Iter, _Proj>> _Pred>
   _LIBCPP_HIDE_FROM_ABI constexpr
   subrange<_Iter> operator()(_Iter __first, _Sent __last, _Pred __pred, _Proj __proj = {}) const {
-    // TODO: implement
-    (void)__first; (void)__last; (void)__pred; (void)__proj;
-    return {};
+    return __partition_fn_impl(__first, __last, __pred, __proj);
   }
 
   template <forward_range _Range, class _Proj = identity,
@@ -53,9 +63,7 @@ struct __fn {
   requires permutable<iterator_t<_Range>>
   _LIBCPP_HIDE_FROM_ABI constexpr
   borrowed_subrange_t<_Range> operator()(_Range&& __range, _Pred __pred, _Proj __proj = {}) const {
-    // TODO: implement
-    (void)__range; (void)__pred; (void)__proj;
-    return {};
+    return __partition_fn_impl(ranges::begin(__range), ranges::end(__range), __pred, __proj);
   }
 
 };

diff  --git a/libcxx/include/__algorithm/ranges_stable_partition.h b/libcxx/include/__algorithm/ranges_stable_partition.h
index 178c953ebdae2..27957db8829f1 100644
--- a/libcxx/include/__algorithm/ranges_stable_partition.h
+++ b/libcxx/include/__algorithm/ranges_stable_partition.h
@@ -9,7 +9,9 @@
 #ifndef _LIBCPP___ALGORITHM_RANGES_STABLE_PARTITION_H
 #define _LIBCPP___ALGORITHM_RANGES_STABLE_PARTITION_H
 
+#include <__algorithm/iterator_operations.h>
 #include <__algorithm/make_projected.h>
+#include <__algorithm/ranges_iterator_concept.h>
 #include <__algorithm/stable_partition.h>
 #include <__config>
 #include <__functional/identity.h>
@@ -17,6 +19,7 @@
 #include <__functional/ranges_operations.h>
 #include <__iterator/concepts.h>
 #include <__iterator/iterator_traits.h>
+#include <__iterator/next.h>
 #include <__iterator/permutable.h>
 #include <__iterator/projected.h>
 #include <__ranges/access.h>
@@ -25,6 +28,7 @@
 #include <__ranges/subrange.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
+#include <type_traits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -39,14 +43,25 @@ namespace __stable_partition {
 
 struct __fn {
 
+  template <class _Iter, class _Sent, class _Proj, class _Pred>
+  _LIBCPP_HIDE_FROM_ABI static
+  subrange<__uncvref_t<_Iter>> __stable_partition_fn_impl(
+      _Iter&& __first, _Sent&& __last, _Pred&& __pred, _Proj&& __proj) {
+    auto __last_iter = ranges::next(__first, __last);
+
+    auto&& __projected_pred = ranges::__make_projected_pred(__pred, __proj);
+    auto __result = std::__stable_partition<_RangeAlgPolicy>(
+        std::move(__first), __last_iter, __projected_pred, __iterator_concept<_Iter>());
+
+    return {std::move(__result), std::move(__last_iter)};
+  }
+
   template <bidirectional_iterator _Iter, sentinel_for<_Iter> _Sent, class _Proj = identity,
             indirect_unary_predicate<projected<_Iter, _Proj>> _Pred>
   requires permutable<_Iter>
   _LIBCPP_HIDE_FROM_ABI
   subrange<_Iter> operator()(_Iter __first, _Sent __last, _Pred __pred, _Proj __proj = {}) const {
-    // TODO: implement
-    (void)__first; (void)__last; (void)__pred; (void)__proj;
-    return {};
+    return __stable_partition_fn_impl(__first, __last, __pred, __proj);
   }
 
   template <bidirectional_range _Range, class _Proj = identity,
@@ -54,9 +69,7 @@ struct __fn {
   requires permutable<iterator_t<_Range>>
   _LIBCPP_HIDE_FROM_ABI
   borrowed_subrange_t<_Range> operator()(_Range&& __range, _Pred __pred, _Proj __proj = {}) const {
-    // TODO: implement
-    (void)__range; (void)__pred; (void)__proj;
-    return {};
+    return __stable_partition_fn_impl(ranges::begin(__range), ranges::end(__range), __pred, __proj);
   }
 
 };

diff  --git a/libcxx/include/__algorithm/rotate.h b/libcxx/include/__algorithm/rotate.h
index c9ea5bad4c5ad..fcf8444a65a08 100644
--- a/libcxx/include/__algorithm/rotate.h
+++ b/libcxx/include/__algorithm/rotate.h
@@ -9,6 +9,7 @@
 #ifndef _LIBCPP___ALGORITHM_ROTATE_H
 #define _LIBCPP___ALGORITHM_ROTATE_H
 
+#include <__algorithm/iterator_operations.h>
 #include <__algorithm/move.h>
 #include <__algorithm/move_backward.h>
 #include <__algorithm/swap_ranges.h>
@@ -26,37 +27,40 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _ForwardIterator>
+template <class _AlgPolicy, class _ForwardIterator>
 _LIBCPP_CONSTEXPR_AFTER_CXX11 _ForwardIterator
 __rotate_left(_ForwardIterator __first, _ForwardIterator __last)
 {
     typedef typename iterator_traits<_ForwardIterator>::value_type value_type;
-    value_type __tmp = _VSTD::move(*__first);
+    value_type __tmp = _IterOps<_AlgPolicy>::__iter_move(__first);
+    // TODO(ranges): pass `_AlgPolicy` to `move`.
     _ForwardIterator __lm1 = _VSTD::move(_VSTD::next(__first), __last, __first);
     *__lm1 = _VSTD::move(__tmp);
     return __lm1;
 }
 
-template <class _BidirectionalIterator>
+template <class _AlgPolicy, class _BidirectionalIterator>
 _LIBCPP_CONSTEXPR_AFTER_CXX11 _BidirectionalIterator
 __rotate_right(_BidirectionalIterator __first, _BidirectionalIterator __last)
 {
     typedef typename iterator_traits<_BidirectionalIterator>::value_type value_type;
+    // TODO(ranges): pass `_AlgPolicy` to `prev`.
     _BidirectionalIterator __lm1 = _VSTD::prev(__last);
-    value_type __tmp = _VSTD::move(*__lm1);
+    value_type __tmp = _IterOps<_AlgPolicy>::__iter_move(__lm1);
+    // TODO(ranges): pass `_AlgPolicy` to `move_backward`.
     _BidirectionalIterator __fp1 = _VSTD::move_backward(__first, __lm1, __last);
     *__first = _VSTD::move(__tmp);
     return __fp1;
 }
 
-template <class _ForwardIterator>
+template <class _AlgPolicy, class _ForwardIterator>
 _LIBCPP_CONSTEXPR_AFTER_CXX14 _ForwardIterator
 __rotate_forward(_ForwardIterator __first, _ForwardIterator __middle, _ForwardIterator __last)
 {
     _ForwardIterator __i = __middle;
     while (true)
     {
-        swap(*__first, *__i);
+        _IterOps<_AlgPolicy>::iter_swap(__first, __i);
         ++__first;
         if (++__i == __last)
             break;
@@ -69,7 +73,7 @@ __rotate_forward(_ForwardIterator __first, _ForwardIterator __middle, _ForwardIt
         __i = __middle;
         while (true)
         {
-            swap(*__first, *__i);
+            _IterOps<_AlgPolicy>::iter_swap(__first, __i);
             ++__first;
             if (++__i == __last)
             {
@@ -98,7 +102,7 @@ __algo_gcd(_Integral __x, _Integral __y)
     return __x;
 }
 
-template<typename _RandomAccessIterator>
+template <class _AlgPolicy, typename _RandomAccessIterator>
 _LIBCPP_CONSTEXPR_AFTER_CXX14 _RandomAccessIterator
 __rotate_gcd(_RandomAccessIterator __first, _RandomAccessIterator __middle, _RandomAccessIterator __last)
 {
@@ -109,18 +113,19 @@ __rotate_gcd(_RandomAccessIterator __first, _RandomAccessIterator __middle, _Ran
     const 
diff erence_type __m2 = __last - __middle;
     if (__m1 == __m2)
     {
+        // TODO(ranges): pass `_AlgPolicy` to `swap_ranges`.
         _VSTD::swap_ranges(__first, __middle, __middle);
         return __middle;
     }
     const 
diff erence_type __g = _VSTD::__algo_gcd(__m1, __m2);
     for (_RandomAccessIterator __p = __first + __g; __p != __first;)
     {
-        value_type __t(_VSTD::move(*--__p));
+        value_type __t(_IterOps<_AlgPolicy>::__iter_move(--__p));
         _RandomAccessIterator __p1 = __p;
         _RandomAccessIterator __p2 = __p1 + __m1;
         do
         {
-            *__p1 = _VSTD::move(*__p2);
+            *__p1 = _IterOps<_AlgPolicy>::__iter_move(__p2);
             __p1 = __p2;
             const 
diff erence_type __d = __last - __p2;
             if (__m1 < __d)
@@ -133,54 +138,66 @@ __rotate_gcd(_RandomAccessIterator __first, _RandomAccessIterator __middle, _Ran
     return __first + __m2;
 }
 
-template <class _ForwardIterator>
+template <class _AlgPolicy, class _ForwardIterator>
 inline _LIBCPP_INLINE_VISIBILITY
 _LIBCPP_CONSTEXPR_AFTER_CXX11 _ForwardIterator
-__rotate(_ForwardIterator __first, _ForwardIterator __middle, _ForwardIterator __last,
+__rotate_impl(_ForwardIterator __first, _ForwardIterator __middle, _ForwardIterator __last,
          _VSTD::forward_iterator_tag)
 {
     typedef typename iterator_traits<_ForwardIterator>::value_type value_type;
     if (is_trivially_move_assignable<value_type>::value)
     {
-        if (_VSTD::next(__first) == __middle)
-            return _VSTD::__rotate_left(__first, __last);
+        if (_IterOps<_AlgPolicy>::next(__first) == __middle)
+            return std::__rotate_left<_AlgPolicy>(__first, __last);
     }
-    return _VSTD::__rotate_forward(__first, __middle, __last);
+    return std::__rotate_forward<_AlgPolicy>(__first, __middle, __last);
 }
 
-template <class _BidirectionalIterator>
+template <class _AlgPolicy, class _BidirectionalIterator>
 inline _LIBCPP_INLINE_VISIBILITY
 _LIBCPP_CONSTEXPR_AFTER_CXX11 _BidirectionalIterator
-__rotate(_BidirectionalIterator __first, _BidirectionalIterator __middle, _BidirectionalIterator __last,
+__rotate_impl(_BidirectionalIterator __first, _BidirectionalIterator __middle, _BidirectionalIterator __last,
          bidirectional_iterator_tag)
 {
     typedef typename iterator_traits<_BidirectionalIterator>::value_type value_type;
     if (is_trivially_move_assignable<value_type>::value)
     {
-        if (_VSTD::next(__first) == __middle)
-            return _VSTD::__rotate_left(__first, __last);
-        if (_VSTD::next(__middle) == __last)
-            return _VSTD::__rotate_right(__first, __last);
+        if (_IterOps<_AlgPolicy>::next(__first) == __middle)
+            return std::__rotate_left<_AlgPolicy>(__first, __last);
+        if (_IterOps<_AlgPolicy>::next(__middle) == __last)
+            return std::__rotate_right<_AlgPolicy>(__first, __last);
     }
-    return _VSTD::__rotate_forward(__first, __middle, __last);
+    return std::__rotate_forward<_AlgPolicy>(__first, __middle, __last);
 }
 
-template <class _RandomAccessIterator>
+template <class _AlgPolicy, class _RandomAccessIterator>
 inline _LIBCPP_INLINE_VISIBILITY
 _LIBCPP_CONSTEXPR_AFTER_CXX11 _RandomAccessIterator
-__rotate(_RandomAccessIterator __first, _RandomAccessIterator __middle, _RandomAccessIterator __last,
+__rotate_impl(_RandomAccessIterator __first, _RandomAccessIterator __middle, _RandomAccessIterator __last,
          random_access_iterator_tag)
 {
     typedef typename iterator_traits<_RandomAccessIterator>::value_type value_type;
     if (is_trivially_move_assignable<value_type>::value)
     {
-        if (_VSTD::next(__first) == __middle)
-            return _VSTD::__rotate_left(__first, __last);
-        if (_VSTD::next(__middle) == __last)
-            return _VSTD::__rotate_right(__first, __last);
-        return _VSTD::__rotate_gcd(__first, __middle, __last);
+        if (_IterOps<_AlgPolicy>::next(__first) == __middle)
+            return std::__rotate_left<_AlgPolicy>(__first, __last);
+        if (_IterOps<_AlgPolicy>::next(__middle) == __last)
+            return std::__rotate_right<_AlgPolicy>(__first, __last);
+        return std::__rotate_gcd<_AlgPolicy>(__first, __middle, __last);
     }
-    return _VSTD::__rotate_forward(__first, __middle, __last);
+    return std::__rotate_forward<_AlgPolicy>(__first, __middle, __last);
+}
+
+template <class _AlgPolicy, class _RandomAccessIterator, class _IterCategory>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX11
+_RandomAccessIterator __rotate(_RandomAccessIterator __first, _RandomAccessIterator __middle,
+    _RandomAccessIterator __last, _IterCategory __iter_category) {
+  if (__first == __middle)
+      return __last;
+  if (__middle == __last)
+      return __first;
+
+  return std::__rotate_impl<_AlgPolicy>(std::move(__first), std::move(__middle), std::move(__last), __iter_category);
 }
 
 template <class _ForwardIterator>
@@ -188,12 +205,8 @@ inline _LIBCPP_INLINE_VISIBILITY
 _LIBCPP_CONSTEXPR_AFTER_CXX17 _ForwardIterator
 rotate(_ForwardIterator __first, _ForwardIterator __middle, _ForwardIterator __last)
 {
-    if (__first == __middle)
-        return __last;
-    if (__middle == __last)
-        return __first;
-    return _VSTD::__rotate(__first, __middle, __last,
-                           typename iterator_traits<_ForwardIterator>::iterator_category());
+  return std::__rotate<_ClassicAlgPolicy>(__first, __middle, __last,
+                                          typename iterator_traits<_ForwardIterator>::iterator_category());
 }
 
 _LIBCPP_END_NAMESPACE_STD

diff  --git a/libcxx/include/__algorithm/stable_partition.h b/libcxx/include/__algorithm/stable_partition.h
index 969ac7a6173e6..e5ad48b2ed516 100644
--- a/libcxx/include/__algorithm/stable_partition.h
+++ b/libcxx/include/__algorithm/stable_partition.h
@@ -9,13 +9,14 @@
 #ifndef _LIBCPP___ALGORITHM_STABLE_PARTITION_H
 #define _LIBCPP___ALGORITHM_STABLE_PARTITION_H
 
+#include <__algorithm/iterator_operations.h>
 #include <__algorithm/rotate.h>
 #include <__config>
 #include <__iterator/advance.h>
 #include <__iterator/distance.h>
 #include <__iterator/iterator_traits.h>
-#include <__utility/swap.h>
 #include <memory>
+#include <type_traits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -23,11 +24,13 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _Predicate, class _ForwardIterator, class _Distance, class _Pair>
+template <class _AlgPolicy, class _Predicate, class _ForwardIterator, class _Distance, class _Pair>
 _ForwardIterator
-__stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate __pred,
+__stable_partition_impl(_ForwardIterator __first, _ForwardIterator __last, _Predicate __pred,
                    _Distance __len, _Pair __p, forward_iterator_tag __fit)
 {
+    using _Ops = _IterOps<_AlgPolicy>;
+
     // *__first is known to be false
     // __len >= 1
     if (__len == 1)
@@ -37,7 +40,7 @@ __stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate
         _ForwardIterator __m = __first;
         if (__pred(*++__m))
         {
-            swap(*__first, *__m);
+            _Ops::iter_swap(__first, __m);
             return __m;
         }
         return __first;
@@ -50,7 +53,7 @@ __stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate
         // Move the falses into the temporary buffer, and the trues to the front of the line
         // Update __first to always point to the end of the trues
         value_type* __t = __p.first;
-        ::new ((void*)__t) value_type(_VSTD::move(*__first));
+        ::new ((void*)__t) value_type(_Ops::__iter_move(__first));
         __d.template __incr<value_type>();
         ++__t;
         _ForwardIterator __i = __first;
@@ -58,12 +61,12 @@ __stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate
         {
             if (__pred(*__i))
             {
-                *__first = _VSTD::move(*__i);
+                *__first = _Ops::__iter_move(__i);
                 ++__first;
             }
             else
             {
-                ::new ((void*)__t) value_type(_VSTD::move(*__i));
+                ::new ((void*)__t) value_type(_Ops::__iter_move(__i));
                 __d.template __incr<value_type>();
                 ++__t;
             }
@@ -72,7 +75,7 @@ __stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate
         // Move falses back into range, but don't mess up __first which points to first false
         __i = __first;
         for (value_type* __t2 = __p.first; __t2 < __t; ++__t2, (void) ++__i)
-            *__i = _VSTD::move(*__t2);
+            *__i = _Ops::__iter_move(__t2);
         // __h destructs moved-from values out of the temp buffer, but doesn't deallocate buffer
         return __first;
     }
@@ -80,11 +83,12 @@ __stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate
     // __len >= 3
     _ForwardIterator __m = __first;
     _Distance __len2 = __len / 2;  // __len2 >= 2
-    _VSTD::advance(__m, __len2);
+    _Ops::advance(__m, __len2);
     // recurse on [__first, __m), *__first know to be false
     // F?????????????????
     // f       m         l
-    _ForwardIterator __first_false = _VSTD::__stable_partition<_Predicate&>(__first, __m, __pred, __len2, __p, __fit);
+    _ForwardIterator __first_false = std::__stable_partition_impl<_AlgPolicy, _Predicate&>(
+        __first, __m, __pred, __len2, __p, __fit);
     // TTTFFFFF??????????
     // f  ff   m         l
     // recurse on [__m, __last], except increase __m until *(__m) is false, *__last know to be true
@@ -99,18 +103,19 @@ __stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate
     }
     // TTTFFFFFTTTF??????
     // f  ff   m  m1     l
-    __second_false = _VSTD::__stable_partition<_Predicate&>(__m1, __last, __pred, __len_half, __p, __fit);
+    __second_false = std::__stable_partition_impl<_AlgPolicy, _Predicate&>(
+        __m1, __last, __pred, __len_half, __p, __fit);
 __second_half_done:
     // TTTFFFFFTTTTTFFFFF
     // f  ff   m    sf   l
-    return _VSTD::rotate(__first_false, __m, __second_false);
+    return std::__rotate<_AlgPolicy>(__first_false, __m, __second_false, __fit);
     // TTTTTTTTFFFFFFFFFF
     //         |
 }
 
-template <class _Predicate, class _ForwardIterator>
+template <class _AlgPolicy, class _Predicate, class _ForwardIterator>
 _ForwardIterator
-__stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate __pred,
+__stable_partition_impl(_ForwardIterator __first, _ForwardIterator __last, _Predicate __pred,
                    forward_iterator_tag)
 {
     const unsigned __alloc_limit = 3;  // might want to make this a function of trivial assignment
@@ -127,7 +132,7 @@ __stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate
     // *__first is known to be false
     typedef typename iterator_traits<_ForwardIterator>::
diff erence_type 
diff erence_type;
     typedef typename iterator_traits<_ForwardIterator>::value_type value_type;
-    
diff erence_type __len = _VSTD::distance(__first, __last);
+    
diff erence_type __len = _IterOps<_AlgPolicy>::distance(__first, __last);
     pair<value_type*, ptr
diff _t> __p(0, 0);
     unique_ptr<value_type, __return_temporary_buffer> __h;
     if (__len >= __alloc_limit)
@@ -138,20 +143,23 @@ _LIBCPP_SUPPRESS_DEPRECATED_PUSH
 _LIBCPP_SUPPRESS_DEPRECATED_POP
         __h.reset(__p.first);
     }
-    return _VSTD::__stable_partition<_Predicate&>(__first, __last, __pred, __len, __p, forward_iterator_tag());
+    return std::__stable_partition_impl<_AlgPolicy, _Predicate&>(
+        std::move(__first), std::move(__last), __pred, __len, __p, forward_iterator_tag());
 }
 
-template <class _Predicate, class _BidirectionalIterator, class _Distance, class _Pair>
+template <class _AlgPolicy, class _Predicate, class _BidirectionalIterator, class _Distance, class _Pair>
 _BidirectionalIterator
-__stable_partition(_BidirectionalIterator __first, _BidirectionalIterator __last, _Predicate __pred,
+__stable_partition_impl(_BidirectionalIterator __first, _BidirectionalIterator __last, _Predicate __pred,
                    _Distance __len, _Pair __p, bidirectional_iterator_tag __bit)
 {
+    using _Ops = _IterOps<_AlgPolicy>;
+
     // *__first is known to be false
     // *__last is known to be true
     // __len >= 2
     if (__len == 2)
     {
-        swap(*__first, *__last);
+        _Ops::iter_swap(__first, __last);
         return __last;
     }
     if (__len == 3)
@@ -159,12 +167,12 @@ __stable_partition(_BidirectionalIterator __first, _BidirectionalIterator __last
         _BidirectionalIterator __m = __first;
         if (__pred(*++__m))
         {
-            swap(*__first, *__m);
-            swap(*__m, *__last);
+            _Ops::iter_swap(__first, __m);
+            _Ops::iter_swap(__m, __last);
             return __last;
         }
-        swap(*__m, *__last);
-        swap(*__first, *__m);
+        _Ops::iter_swap(__m, __last);
+        _Ops::iter_swap(__first, __m);
         return __m;
     }
     if (__len <= __p.second)
@@ -175,7 +183,7 @@ __stable_partition(_BidirectionalIterator __first, _BidirectionalIterator __last
         // Move the falses into the temporary buffer, and the trues to the front of the line
         // Update __first to always point to the end of the trues
         value_type* __t = __p.first;
-        ::new ((void*)__t) value_type(_VSTD::move(*__first));
+        ::new ((void*)__t) value_type(_Ops::__iter_move(__first));
         __d.template __incr<value_type>();
         ++__t;
         _BidirectionalIterator __i = __first;
@@ -183,23 +191,23 @@ __stable_partition(_BidirectionalIterator __first, _BidirectionalIterator __last
         {
             if (__pred(*__i))
             {
-                *__first = _VSTD::move(*__i);
+                *__first = _Ops::__iter_move(__i);
                 ++__first;
             }
             else
             {
-                ::new ((void*)__t) value_type(_VSTD::move(*__i));
+                ::new ((void*)__t) value_type(_Ops::__iter_move(__i));
                 __d.template __incr<value_type>();
                 ++__t;
             }
         }
         // move *__last, known to be true
-        *__first = _VSTD::move(*__i);
+        *__first = _Ops::__iter_move(__i);
         __i = ++__first;
         // All trues now at start of range, all falses in buffer
         // Move falses back into range, but don't mess up __first which points to first false
         for (value_type* __t2 = __p.first; __t2 < __t; ++__t2, (void) ++__i)
-            *__i = _VSTD::move(*__t2);
+            *__i = _Ops::__iter_move(__t2);
         // __h destructs moved-from values out of the temp buffer, but doesn't deallocate buffer
         return __first;
     }
@@ -207,7 +215,7 @@ __stable_partition(_BidirectionalIterator __first, _BidirectionalIterator __last
     // __len >= 4
     _BidirectionalIterator __m = __first;
     _Distance __len2 = __len / 2;  // __len2 >= 2
-    _VSTD::advance(__m, __len2);
+    _Ops::advance(__m, __len2);
     // recurse on [__first, __m-1], except reduce __m-1 until *(__m-1) is true, *__first know to be false
     // F????????????????T
     // f       m        l
@@ -222,7 +230,8 @@ __stable_partition(_BidirectionalIterator __first, _BidirectionalIterator __last
     }
     // F???TFFF?????????T
     // f   m1  m        l
-    __first_false = _VSTD::__stable_partition<_Predicate&>(__first, __m1, __pred, __len_half, __p, __bit);
+    __first_false = std::__stable_partition_impl<_AlgPolicy, _Predicate&>(
+        __first, __m1, __pred, __len_half, __p, __bit);
 __first_half_done:
     // TTTFFFFF?????????T
     // f  ff   m        l
@@ -239,18 +248,19 @@ __stable_partition(_BidirectionalIterator __first, _BidirectionalIterator __last
     }
     // TTTFFFFFTTTF?????T
     // f  ff   m  m1    l
-    __second_false = _VSTD::__stable_partition<_Predicate&>(__m1, __last, __pred, __len_half, __p, __bit);
+    __second_false = std::__stable_partition_impl<_AlgPolicy, _Predicate&>(
+        __m1, __last, __pred, __len_half, __p, __bit);
 __second_half_done:
     // TTTFFFFFTTTTTFFFFF
     // f  ff   m    sf  l
-    return _VSTD::rotate(__first_false, __m, __second_false);
+    return std::__rotate<_AlgPolicy>(__first_false, __m, __second_false, __bit);
     // TTTTTTTTFFFFFFFFFF
     //         |
 }
 
-template <class _Predicate, class _BidirectionalIterator>
+template <class _AlgPolicy, class _Predicate, class _BidirectionalIterator>
 _BidirectionalIterator
-__stable_partition(_BidirectionalIterator __first, _BidirectionalIterator __last, _Predicate __pred,
+__stable_partition_impl(_BidirectionalIterator __first, _BidirectionalIterator __last, _Predicate __pred,
                    bidirectional_iterator_tag)
 {
     typedef typename iterator_traits<_BidirectionalIterator>::
diff erence_type 
diff erence_type;
@@ -276,7 +286,7 @@ __stable_partition(_BidirectionalIterator __first, _BidirectionalIterator __last
     // *__first is known to be false
     // *__last is known to be true
     // __len >= 2
-    
diff erence_type __len = _VSTD::distance(__first, __last) + 1;
+    
diff erence_type __len = _IterOps<_AlgPolicy>::distance(__first, __last) + 1;
     pair<value_type*, ptr
diff _t> __p(0, 0);
     unique_ptr<value_type, __return_temporary_buffer> __h;
     if (__len >= __alloc_limit)
@@ -287,7 +297,16 @@ _LIBCPP_SUPPRESS_DEPRECATED_PUSH
 _LIBCPP_SUPPRESS_DEPRECATED_POP
         __h.reset(__p.first);
     }
-    return _VSTD::__stable_partition<_Predicate&>(__first, __last, __pred, __len, __p, bidirectional_iterator_tag());
+    return std::__stable_partition_impl<_AlgPolicy, _Predicate&>(
+        std::move(__first), std::move(__last), __pred, __len, __p, bidirectional_iterator_tag());
+}
+
+template <class _AlgPolicy, class _Predicate, class _ForwardIterator, class _IterCategory>
+_LIBCPP_HIDE_FROM_ABI
+_ForwardIterator __stable_partition(
+    _ForwardIterator __first, _ForwardIterator __last, _Predicate&& __pred, _IterCategory __iter_category) {
+  return std::__stable_partition_impl<_AlgPolicy, __uncvref_t<_Predicate>&>(
+      std::move(__first), std::move(__last), __pred, __iter_category);
 }
 
 template <class _ForwardIterator, class _Predicate>
@@ -295,7 +314,9 @@ inline _LIBCPP_INLINE_VISIBILITY
 _ForwardIterator
 stable_partition(_ForwardIterator __first, _ForwardIterator __last, _Predicate __pred)
 {
-    return _VSTD::__stable_partition<_Predicate&>(__first, __last, __pred, typename iterator_traits<_ForwardIterator>::iterator_category());
+  using _IterCategory = typename iterator_traits<_ForwardIterator>::iterator_category;
+  return std::__stable_partition<_ClassicAlgPolicy, _Predicate&>(
+      std::move(__first), std::move(__last), __pred, _IterCategory());
 }
 
 _LIBCPP_END_NAMESPACE_STD

diff  --git a/libcxx/include/algorithm b/libcxx/include/algorithm
index ff700cf29a2bf..cc2c468c2976e 100644
--- a/libcxx/include/algorithm
+++ b/libcxx/include/algorithm
@@ -464,6 +464,28 @@ namespace ranges {
              ranges::less>
     constexpr bool binary_search(R&& r, const T& value, Comp comp = {},
                                          Proj proj = {});                                     // since C++20
+
+  template<permutable I, sentinel_for<I> S, class Proj = identity,
+           indirect_unary_predicate<projected<I, Proj>> Pred>
+    constexpr subrange<I>
+      partition(I first, S last, Pred pred, Proj proj = {});                                  // Since C++20
+
+  template<forward_range R, class Proj = identity,
+           indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
+    requires permutable<iterator_t<R>>
+    constexpr borrowed_subrange_t<R>
+      partition(R&& r, Pred pred, Proj proj = {});                                            // Since C++20
+
+  template<bidirectional_iterator I, sentinel_for<I> S, class Proj = identity,
+           indirect_unary_predicate<projected<I, Proj>> Pred>
+    requires permutable<I>
+    subrange<I> stable_partition(I first, S last, Pred pred, Proj proj = {});                 // Since C++20
+
+  template<bidirectional_range R, class Proj = identity,
+           indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
+    requires permutable<iterator_t<R>>
+    borrowed_subrange_t<R> stable_partition(R&& r, Pred pred, Proj proj = {});                // Since C++20
+
   template<input_iterator I1, sentinel_for<I1> S1, forward_iterator I2, sentinel_for<I2> S2,
            class Pred = ranges::equal_to, class Proj1 = identity, class Proj2 = identity>
     requires indirectly_comparable<I1, I2, Pred, Proj1, Proj2>
@@ -1496,6 +1518,7 @@ template <class BidirectionalIterator, class Compare>
 #include <__algorithm/ranges_move_backward.h>
 #include <__algorithm/ranges_none_of.h>
 #include <__algorithm/ranges_nth_element.h>
+#include <__algorithm/ranges_partition.h>
 #include <__algorithm/ranges_pop_heap.h>
 #include <__algorithm/ranges_push_heap.h>
 #include <__algorithm/ranges_remove.h>
@@ -1513,6 +1536,7 @@ template <class BidirectionalIterator, class Compare>
 #include <__algorithm/ranges_set_union.h>
 #include <__algorithm/ranges_sort.h>
 #include <__algorithm/ranges_sort_heap.h>
+#include <__algorithm/ranges_stable_partition.h>
 #include <__algorithm/ranges_stable_sort.h>
 #include <__algorithm/ranges_swap_ranges.h>
 #include <__algorithm/ranges_transform.h>

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 25e1c6e6d0e2b..91ac0d2c753d7 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -337,6 +337,7 @@ module std [system] {
       module ranges_is_partitioned           { private header "__algorithm/ranges_is_partitioned.h" }
       module ranges_is_sorted                { private header "__algorithm/ranges_is_sorted.h" }
       module ranges_is_sorted_until          { private header "__algorithm/ranges_is_sorted_until.h" }
+      module ranges_iterator_concept         { private header "__algorithm/ranges_iterator_concept.h" }
       module ranges_lexicographical_compare  { private header "__algorithm/ranges_lexicographical_compare.h" }
       module ranges_lower_bound              { private header "__algorithm/ranges_lower_bound.h" }
       module ranges_make_heap                { private header "__algorithm/ranges_make_heap.h" }

diff  --git a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp
index c43ad9c7145d3..d980ba32efc0c 100644
--- a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp
+++ b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp
@@ -175,8 +175,8 @@ constexpr bool all_the_algorithms()
     //(void)std::ranges::partial_sort(a, mid, Less(&copies)); assert(copies == 0);
     //(void)std::ranges::partial_sort_copy(first, last, first2, mid2, Less(&copies)); assert(copies == 0);
     //(void)std::ranges::partial_sort_copy(a, b, Less(&copies)); assert(copies == 0);
-    //(void)std::ranges::partition(first, last, UnaryTrue(&copies)); assert(copies == 0);
-    //(void)std::ranges::partition(a, UnaryTrue(&copies)); assert(copies == 0);
+    (void)std::ranges::partition(first, last, UnaryTrue(&copies)); assert(copies == 0);
+    (void)std::ranges::partition(a, UnaryTrue(&copies)); assert(copies == 0);
     //(void)std::ranges::partition_copy(first, last, first2, last2, UnaryTrue(&copies)); assert(copies == 0);
     //(void)std::ranges::partition_copy(a, first2, last2, UnaryTrue(&copies)); assert(copies == 0);
     //(void)std::ranges::partition_point(first, last, UnaryTrue(&copies)); assert(copies == 0);
@@ -211,8 +211,8 @@ constexpr bool all_the_algorithms()
     (void)std::ranges::sort(a, Less(&copies)); assert(copies == 0);
     (void)std::ranges::sort_heap(first, last, Less(&copies)); assert(copies == 0);
     (void)std::ranges::sort_heap(a, Less(&copies)); assert(copies == 0);
-    //if (!std::is_constant_evaluated()) { (void)std::ranges::stable_partition(first, last, UnaryTrue(&copies)); assert(copies == 0); }
-    //if (!std::is_constant_evaluated()) { (void)std::ranges::stable_partition(a, UnaryTrue(&copies)); assert(copies == 0); }
+    if (!std::is_constant_evaluated()) { (void)std::ranges::stable_partition(first, last, UnaryTrue(&copies)); assert(copies == 0); }
+    if (!std::is_constant_evaluated()) { (void)std::ranges::stable_partition(a, UnaryTrue(&copies)); assert(copies == 0); }
     if (!std::is_constant_evaluated()) { (void)std::ranges::stable_sort(first, last, Less(&copies)); assert(copies == 0); }
     if (!std::is_constant_evaluated()) { (void)std::ranges::stable_sort(a, Less(&copies)); assert(copies == 0); }
 #if TEST_STD_VER > 20

diff  --git a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp
index b0740eaae2114..0a5758699e4a3 100644
--- a/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp
+++ b/libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp
@@ -158,8 +158,8 @@ constexpr bool all_the_algorithms()
     //(void)std::ranges::partial_sort(a, mid, Less(), Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::partial_sort_copy(first, last, first2, mid2, Less(), Proj(&copies), Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::partial_sort_copy(a, b, Less(), Proj(&copies), Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::partition(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::partition(a, UnaryTrue(), Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::partition(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::partition(a, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::partition_copy(first, last, first2, last2, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::partition_copy(a, first2, last2, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     //(void)std::ranges::partition_point(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0);
@@ -202,8 +202,8 @@ constexpr bool all_the_algorithms()
     (void)std::ranges::sort(a, Less(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::sort_heap(first, last, Less(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::sort_heap(a, Less(), Proj(&copies)); assert(copies == 0);
-    //if (!std::is_constant_evaluated()) { (void)std::ranges::stable_partition(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0); }
-    //if (!std::is_constant_evaluated()) { (void)std::ranges::stable_partition(a, UnaryTrue(), Proj(&copies)); assert(copies == 0); }
+    if (!std::is_constant_evaluated()) { (void)std::ranges::stable_partition(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0); }
+    if (!std::is_constant_evaluated()) { (void)std::ranges::stable_partition(a, UnaryTrue(), Proj(&copies)); assert(copies == 0); }
     if (!std::is_constant_evaluated()) { (void)std::ranges::stable_sort(first, last, Less(), Proj(&copies)); assert(copies == 0); }
     if (!std::is_constant_evaluated()) { (void)std::ranges::stable_sort(a, Less(), Proj(&copies)); assert(copies == 0); }
 #if TEST_STD_VER > 20

diff  --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp
index 8e2c43bfbd3a0..dfed6560a7d3d 100644
--- a/libcxx/test/libcxx/private_headers.verify.cpp
+++ b/libcxx/test/libcxx/private_headers.verify.cpp
@@ -135,6 +135,7 @@ END-SCRIPT
 #include <__algorithm/ranges_is_partitioned.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_is_partitioned.h'}}
 #include <__algorithm/ranges_is_sorted.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_is_sorted.h'}}
 #include <__algorithm/ranges_is_sorted_until.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_is_sorted_until.h'}}
+#include <__algorithm/ranges_iterator_concept.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_iterator_concept.h'}}
 #include <__algorithm/ranges_lexicographical_compare.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_lexicographical_compare.h'}}
 #include <__algorithm/ranges_lower_bound.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_lower_bound.h'}}
 #include <__algorithm/ranges_make_heap.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_make_heap.h'}}

diff  --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_partition.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_partition.pass.cpp
index 1863bade51426..4938f5f52e972 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_partition.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_partition.pass.cpp
@@ -31,12 +31,174 @@
 #include "almost_satisfies_types.h"
 #include "test_iterators.h"
 
-// TODO: SFINAE tests.
+struct UnaryPred { bool operator()(int) const; };
+
+// Test constraints of the (iterator, sentinel) overload.
+// ======================================================
+
+template <class Iter = int*, class Sent = int*, class Pred = UnaryPred>
+concept HasPartitionIter =
+    requires(Iter&& iter, Sent&& sent, Pred&& pred) {
+      std::ranges::partition(std::forward<Iter>(iter), std::forward<Sent>(sent), std::forward<Pred>(pred));
+    };
+
+static_assert(HasPartitionIter<int*, int*, UnaryPred>);
+
+// !permutable<I>
+static_assert(!HasPartitionIter<PermutableNotForwardIterator>);
+static_assert(!HasPartitionIter<PermutableNotSwappable>);
+
+// !sentinel_for<S, I>
+static_assert(!HasPartitionIter<int*, SentinelForNotSemiregular>);
+static_assert(!HasPartitionIter<int*, SentinelForNotWeaklyEqualityComparableWith>);
+
+// !indirect_unary_predicate<projected<I, Proj>>
+static_assert(!HasPartitionIter<int*, int*, IndirectUnaryPredicateNotPredicate>);
+static_assert(!HasPartitionIter<int*, int*, IndirectUnaryPredicateNotCopyConstructible>);
+
+// Test constraints of the (range) overload.
+// =========================================
+
+template <class Range, class Pred>
+concept HasPartitionRange =
+    requires(Range&& range, Pred&& pred) {
+      std::ranges::partition(std::forward<Range>(range), std::forward<Pred>(pred));
+    };
+
+template <class T>
+using R = UncheckedRange<T>;
+
+static_assert(HasPartitionRange<R<int*>, UnaryPred>);
+
+// !forward_range<R>
+static_assert(!HasPartitionRange<ForwardRangeNotDerivedFrom, UnaryPred>);
+static_assert(!HasPartitionRange<ForwardIteratorNotIncrementable, UnaryPred>);
+
+// !indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
+static_assert(!HasPartitionRange<R<int*>, IndirectUnaryPredicateNotPredicate>);
+static_assert(!HasPartitionRange<R<int*>, IndirectUnaryPredicateNotCopyConstructible>);
+
+// !permutable<iterator_t<R>>
+static_assert(!HasPartitionRange<R<PermutableNotForwardIterator>, UnaryPred>);
+static_assert(!HasPartitionRange<R<PermutableNotSwappable>, UnaryPred>);
+
+// `partition` isn't a stable algorithm so this function cannot test the exact output.
+template <class Iter, class Sent, size_t N, class Pred>
+constexpr void test_one(std::array<int, N> input, Pred pred, size_t partition_point) {
+  auto neg_pred = [&](int x) { return !pred(x); };
+
+  { // (iterator, sentinel) overload.
+    auto partitioned = input;
+    auto b = Iter(partitioned.data());
+    auto e = Sent(Iter(partitioned.data() + partitioned.size()));
+
+    std::same_as<std::ranges::subrange<Iter>> decltype(auto) result = std::ranges::partition(b, e, pred);
+
+    assert(base(result.begin()) == partitioned.data() + partition_point);
+    assert(base(result.end()) == partitioned.data() + partitioned.size());
+
+    assert(std::ranges::all_of(b, result.begin(), pred));
+    assert(std::ranges::all_of(result.begin(), e, neg_pred));
+  }
+
+  { // (range) overload.
+    auto partitioned = input;
+    auto b = Iter(partitioned.data());
+    auto e = Sent(Iter(partitioned.data() + partitioned.size()));
+    auto range = std::ranges::subrange(b, e);
+
+    std::same_as<std::ranges::subrange<Iter>> decltype(auto) result = std::ranges::partition(range, pred);
+
+    assert(base(result.begin()) == partitioned.data() + partition_point);
+    assert(base(result.end()) == partitioned.data() + partitioned.size());
+
+    assert(std::ranges::all_of(b, result.begin(), pred));
+    assert(std::ranges::all_of(result.begin(), e, neg_pred));
+  }
+}
+
+template <class Iter, class Sent>
+constexpr void test_iterators_2() {
+  auto is_odd = [](int x) { return x % 2 != 0; };
+
+  // Empty sequence.
+  test_one<Iter, Sent, 0>({}, is_odd, 0);
+  // 1-element sequence, the element satisfies the predicate.
+  test_one<Iter, Sent, 1>({1}, is_odd, 1);
+  // 1-element sequence, the element doesn't satisfy the predicate.
+  test_one<Iter, Sent, 1>({2}, is_odd, 0);
+  // 2-element sequence, not in order.
+  test_one<Iter, Sent, 2>({2, 1}, is_odd, 1);
+  // 2-element sequence, already in order.
+  test_one<Iter, Sent, 2>({1, 2}, is_odd, 1);
+  // 3-element sequence.
+  test_one<Iter, Sent, 3>({2, 1, 3}, is_odd, 2);
+  // Longer sequence.
+  test_one<Iter, Sent, 8>({2, 1, 3, 6, 8, 4, 11, 5}, is_odd, 4);
+  // Longer sequence with duplicates.
+  test_one<Iter, Sent, 8>({2, 1, 3, 6, 2, 8, 1, 6}, is_odd, 3);
+  // All elements are the same and satisfy the predicate.
+  test_one<Iter, Sent, 3>({1, 1, 1}, is_odd, 3);
+  // All elements are the same and don't satisfy the predicate.
+  test_one<Iter, Sent, 3>({2, 2, 2}, is_odd, 0);
+  // Already partitioned.
+  test_one<Iter, Sent, 6>({1, 3, 5, 4, 6, 8}, is_odd, 3);
+  // Reverse-partitioned.
+  test_one<Iter, Sent, 6>({4, 6, 8, 1, 3, 5}, is_odd, 3);
+  // Repeating pattern.
+  test_one<Iter, Sent, 6>({1, 2, 1, 2, 1, 2}, is_odd, 3);
+}
+
+template <class Iter>
+constexpr void test_iterators_1() {
+  test_iterators_2<Iter, Iter>();
+  test_iterators_2<Iter, sentinel_wrapper<Iter>>();
+}
+
+constexpr void test_iterators() {
+  test_iterators_1<forward_iterator<int*>>();
+  test_iterators_1<bidirectional_iterator<int*>>();
+  test_iterators_1<random_access_iterator<int*>>();
+  test_iterators_1<contiguous_iterator<int*>>();
+  test_iterators_1<int*>();
+}
 
 constexpr bool test() {
-  // TODO: main tests.
-  // TODO: A custom comparator works.
-  // TODO: A custom projection works.
+  test_iterators();
+
+  { // A custom projection works.
+    const std::array input = {1, -1};
+    auto is_negative = [](int x) { return x < 0; };
+    auto negate = [](int x) { return -x; };
+    const std::array expected_no_proj = {-1, 1};
+    const std::array expected_with_proj = {1, -1};
+
+    { // (iterator, sentinel) overload.
+      {
+        auto in = input;
+        std::ranges::partition(in.begin(), in.end(), is_negative);
+        assert(in == expected_no_proj);
+      }
+      {
+        auto in = input;
+        std::ranges::partition(in.begin(), in.end(), is_negative, negate);
+        assert(in == expected_with_proj);
+      }
+    }
+
+    { // (range) overload.
+      {
+        auto in = input;
+        std::ranges::partition(in, is_negative);
+        assert(in == expected_no_proj);
+      }
+      {
+        auto in = input;
+        std::ranges::partition(in, is_negative, negate);
+        assert(in == expected_with_proj);
+      }
+    }
+  }
 
   return true;
 }

diff  --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_stable_partition.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_stable_partition.pass.cpp
index e5a9bcb08ac93..6fcaa8e621cb2 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_stable_partition.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.partitions/ranges_stable_partition.pass.cpp
@@ -30,19 +30,216 @@
 #include "almost_satisfies_types.h"
 #include "test_iterators.h"
 
-// TODO: SFINAE tests.
+struct UnaryPred { bool operator()(int) const; };
 
-constexpr bool test() {
-  // TODO: main tests.
-  // TODO: A custom comparator works.
-  // TODO: A custom projection works.
+// Test constraints of the (iterator, sentinel) overload.
+// ======================================================
 
-  return true;
+template <class Iter = int*, class Sent = int*, class Pred = UnaryPred>
+concept HasStablePartitionIter =
+    requires(Iter&& iter, Sent&& sent, Pred&& pred) {
+      std::ranges::stable_partition(std::forward<Iter>(iter), std::forward<Sent>(sent), std::forward<Pred>(pred));
+    };
+
+static_assert(HasStablePartitionIter<int*, int*, UnaryPred>);
+
+// !bidirectional_iterator<I>
+static_assert(!HasStablePartitionIter<BidirectionalIteratorNotDerivedFrom>);
+static_assert(!HasStablePartitionIter<BidirectionalIteratorNotDecrementable>);
+
+// !sentinel_for<S, I>
+static_assert(!HasStablePartitionIter<int*, SentinelForNotSemiregular>);
+static_assert(!HasStablePartitionIter<int*, SentinelForNotWeaklyEqualityComparableWith>);
+
+// !indirect_unary_predicate<projected<I, Proj>>
+static_assert(!HasStablePartitionIter<int*, int*, IndirectUnaryPredicateNotPredicate>);
+static_assert(!HasStablePartitionIter<int*, int*, IndirectUnaryPredicateNotCopyConstructible>);
+
+// !permutable<I>
+static_assert(!HasStablePartitionIter<PermutableNotForwardIterator>);
+static_assert(!HasStablePartitionIter<PermutableNotSwappable>);
+
+// Test constraints of the (range) overload.
+// =========================================
+
+template <class Range, class Pred>
+concept HasStablePartitionRange =
+    requires(Range&& range, Pred&& pred) {
+      std::ranges::stable_partition(std::forward<Range>(range), std::forward<Pred>(pred));
+    };
+
+template <class T>
+using R = UncheckedRange<T>;
+
+static_assert(HasStablePartitionRange<R<int*>, UnaryPred>);
+
+// !bidirectional_range<R>
+static_assert(!HasStablePartitionRange<BidirectionalRangeNotDerivedFrom, UnaryPred>);
+static_assert(!HasStablePartitionRange<BidirectionalRangeNotDecrementable, UnaryPred>);
+
+// !indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
+static_assert(!HasStablePartitionRange<R<int*>, IndirectUnaryPredicateNotPredicate>);
+static_assert(!HasStablePartitionRange<R<int*>, IndirectUnaryPredicateNotCopyConstructible>);
+
+// !permutable<iterator_t<R>>
+static_assert(!HasStablePartitionRange<R<PermutableNotForwardIterator>, UnaryPred>);
+static_assert(!HasStablePartitionRange<R<PermutableNotSwappable>, UnaryPred>);
+
+template <class Iter, class Sent, size_t N, class Pred>
+void test_one(std::array<int, N> input, Pred pred, size_t partition_point, std::array<int, N> expected) {
+  auto neg_pred = [&](int x) { return !pred(x); };
+
+  { // (iterator, sentinel) overload.
+    auto partitioned = input;
+    auto b = Iter(partitioned.data());
+    auto e = Sent(Iter(partitioned.data() + partitioned.size()));
+
+    std::same_as<std::ranges::subrange<Iter>> decltype(auto) result = std::ranges::stable_partition(b, e, pred);
+
+    assert(partitioned == expected);
+    assert(base(result.begin()) == partitioned.data() + partition_point);
+    assert(base(result.end()) == partitioned.data() + partitioned.size());
+
+    assert(std::ranges::all_of(b, result.begin(), pred));
+    assert(std::ranges::all_of(result.begin(), e, neg_pred));
+  }
+
+  { // (range) overload.
+    auto partitioned = input;
+    auto b = Iter(partitioned.data());
+    auto e = Sent(Iter(partitioned.data() + partitioned.size()));
+    auto range = std::ranges::subrange(b, e);
+
+    std::same_as<std::ranges::subrange<Iter>> decltype(auto) result = std::ranges::stable_partition(range, pred);
+
+    assert(partitioned == expected);
+    assert(base(result.begin()) == partitioned.data() + partition_point);
+    assert(base(result.end()) == partitioned.data() + partitioned.size());
+
+    assert(std::ranges::all_of(b, result.begin(), pred));
+    assert(std::ranges::all_of(result.begin(), e, neg_pred));
+  }
+}
+
+template <class Iter, class Sent>
+void test_iterators_2() {
+  auto is_odd = [](int x) { return x % 2 != 0; };
+
+  // Empty sequence.
+  test_one<Iter, Sent, 0>({}, is_odd, 0, {});
+  // 1-element sequence, the element satisfies the predicate.
+  test_one<Iter, Sent, 1>({1}, is_odd, 1, {1});
+  // 1-element sequence, the element doesn't satisfy the predicate.
+  test_one<Iter, Sent, 1>({2}, is_odd, 0, {2});
+  // 2-element sequence, not in order.
+  test_one<Iter, Sent, 2>({2, 1}, is_odd, 1, {1, 2});
+  // 2-element sequence, already in order.
+  test_one<Iter, Sent, 2>({1, 2}, is_odd, 1, {1, 2});
+  // 3-element sequence.
+  test_one<Iter, Sent, 3>({2, 1, 3}, is_odd, 2, {1, 3, 2});
+  // Longer sequence.
+  test_one<Iter, Sent, 8>({2, 1, 3, 6, 8, 4, 11, 5}, is_odd, 4, {1, 3, 11, 5, 2, 6, 8, 4});
+  // Longer sequence with duplicates.
+  test_one<Iter, Sent, 8>({2, 1, 3, 6, 2, 8, 1, 6}, is_odd, 3, {1, 3, 1, 2, 6, 2, 8, 6});
+  // All elements are the same and satisfy the predicate.
+  test_one<Iter, Sent, 3>({1, 1, 1}, is_odd, 3, {1, 1, 1});
+  // All elements are the same and don't satisfy the predicate.
+  test_one<Iter, Sent, 3>({2, 2, 2}, is_odd, 0, {2, 2, 2});
+  // Already partitioned.
+  test_one<Iter, Sent, 6>({1, 3, 5, 4, 6, 8}, is_odd, 3, {1, 3, 5, 4, 6, 8});
+  // Reverse-partitioned.
+  test_one<Iter, Sent, 6>({4, 6, 8, 1, 3, 5}, is_odd, 3, {1, 3, 5, 4, 6, 8});
+  // Repeating pattern.
+  test_one<Iter, Sent, 6>({1, 2, 1, 2, 1, 2}, is_odd, 3, {1, 1, 1, 2, 2, 2});
+}
+
+template <class Iter>
+void test_iterators_1() {
+  test_iterators_2<Iter, Iter>();
+  test_iterators_2<Iter, sentinel_wrapper<Iter>>();
+}
+
+void test_iterators() {
+  test_iterators_1<bidirectional_iterator<int*>>();
+  test_iterators_1<random_access_iterator<int*>>();
+  test_iterators_1<contiguous_iterator<int*>>();
+  test_iterators_1<int*>();
+}
+
+void test() {
+  test_iterators();
+
+  { // The algorithm is stable (equivalent elements remain in the same order).
+    struct OrderedValue {
+      int value;
+      double original_order;
+      bool operator==(const OrderedValue&) const = default;
+    };
+
+    auto is_odd = [](OrderedValue x) { return x.value % 2 != 0; };
+
+    using V = OrderedValue;
+    using Array = std::array<V, 20>;
+    Array orig_in = {
+      V{10, 2.1}, {12, 2.2}, {3, 1.1}, {5, 1.2}, {3, 1.3}, {3, 1.4}, {11, 1.5}, {12, 2.3}, {4, 2.4}, {4, 2.5},
+      {4, 2.6}, {1, 1.6}, {6, 2.7}, {3, 1.7}, {10, 2.8}, {8, 2.9}, {12, 2.10}, {1, 1.8}, {1, 1.9}, {5, 1.10}
+    };
+    Array expected = {
+      V{3, 1.1}, {5, 1.2}, {3, 1.3}, {3, 1.4}, {11, 1.5}, {1, 1.6}, {3, 1.7}, {1, 1.8}, {1, 1.9}, {5, 1.10},
+      {10, 2.1}, {12, 2.2}, {12, 2.3}, {4, 2.4}, {4, 2.5}, {4, 2.6}, {6, 2.7}, {10, 2.8}, {8, 2.9}, {12, 2.10}
+    };
+
+    {
+      auto in = orig_in;
+      std::ranges::stable_partition(in.begin(), in.end(), is_odd);
+      assert(in == expected);
+    }
+
+    {
+      auto in = orig_in;
+      std::ranges::stable_partition(in, is_odd);
+      assert(in == expected);
+    }
+  }
+
+  { // A custom projection works.
+    const std::array input = {1, -1};
+    auto is_negative = [](int x) { return x < 0; };
+    auto negate = [](int x) { return -x; };
+    const std::array expected_no_proj = {-1, 1};
+    const std::array expected_with_proj = {1, -1};
+
+    { // (iterator, sentinel) overload.
+      {
+        auto in = input;
+        std::ranges::partition(in.begin(), in.end(), is_negative);
+        assert(in == expected_no_proj);
+      }
+      {
+        auto in = input;
+        std::ranges::partition(in.begin(), in.end(), is_negative, negate);
+        assert(in == expected_with_proj);
+      }
+    }
+
+    { // (range) overload.
+      {
+        auto in = input;
+        std::ranges::partition(in, is_negative);
+        assert(in == expected_no_proj);
+      }
+      {
+        auto in = input;
+        std::ranges::partition(in, is_negative, negate);
+        assert(in == expected_with_proj);
+      }
+    }
+  }
 }
 
 int main(int, char**) {
   test();
-  static_assert(test());
+  // Note: `stable_partition` is not `constexpr`.
 
   return 0;
 }

diff  --git a/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.compile.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.compile.pass.cpp
index cd8bde50b58ac..439a16b103cec 100644
--- a/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.compile.pass.cpp
+++ b/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.compile.pass.cpp
@@ -156,8 +156,8 @@ void test_all() {
   in2_out_pred(std::ranges::set_union, in, in2, out, binary_pred);
   in_pred(std::ranges::remove_if, in, unary_pred);
   //in_pred(std::ranges::unique, in, binary_pred);
-  //in_pred(std::ranges::partition, in, binary_pred);
-  //in_pred(std::ranges::stable_partition, in, binary_pred);
+  in_pred(std::ranges::partition, in, unary_pred);
+  in_pred(std::ranges::stable_partition, in, unary_pred);
   in_pred(std::ranges::sort, in, binary_pred);
   in_pred(std::ranges::stable_sort, in, binary_pred);
   //in_mid_pred(std::ranges::partial_sort, in, mid, binary_pred);

diff  --git a/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.compile.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.compile.pass.cpp
index 48b20348a295a..7709b88536bbd 100644
--- a/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.compile.pass.cpp
+++ b/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.compile.pass.cpp
@@ -201,8 +201,8 @@ void test_all() {
   // `rotate` has neither a projection nor a predicate.
   // `shuffle` has neither a projection nor a predicate.
   //in_pred(std::ranges::unique, in, &Foo::binary_pred, &Bar::val);
-  //in_pred(std::ranges::partition, in, &Foo::binary_pred, &Bar::val);
-  //in_pred(std::ranges::stable_partition, in, &Foo::binary_pred, &Bar::val);
+  in_pred(std::ranges::partition, in, &Foo::unary_pred, &Bar::val);
+  in_pred(std::ranges::stable_partition, in, &Foo::unary_pred, &Bar::val);
   in_pred(std::ranges::sort, in, &Foo::binary_pred, &Bar::val);
   in_pred(std::ranges::stable_sort, in, &Foo::binary_pred, &Bar::val);
   //in_mid_pred(std::ranges::partial_sort, in, mid, binary_pred);

diff  --git a/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp
index f6eaf15862e3c..fda245d5a6700 100644
--- a/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp
+++ b/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp
@@ -138,9 +138,9 @@ constexpr bool test_all() {
   //test_mid(std::ranges::rotate, in, mid);
   //test(std::ranges::shuffle, in, rand_gen);
   //test(std::ranges::unique, in);
-  //test(std::ranges::partition, in, binary_pred);
-  //if (!std::is_constant_evaluated())
-  //  test(std::ranges::stable_partition, in, binary_pred);
+  test(std::ranges::partition, in, unary_pred);
+  if (!std::is_constant_evaluated())
+    test(std::ranges::stable_partition, in, unary_pred);
   test(std::ranges::sort, in);
   // TODO: `stable_sort` requires `ranges::rotate` to be implemented.
   //if (!std::is_constant_evaluated())

diff  --git a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp
index fec9bfe6a0fb9..84fec22ca9d63 100644
--- a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp
+++ b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp
@@ -112,7 +112,7 @@ static_assert(test(std::ranges::none_of, a, odd));
 static_assert(test(std::ranges::nth_element, a, a+5));
 //static_assert(test(std::ranges::partial_sort, a, a+5));
 //static_assert(test(std::ranges::partial_sort_copy, a, a));
-//static_assert(test(std::ranges::partition, a, odd));
+static_assert(test(std::ranges::partition, a, odd));
 //static_assert(test(std::ranges::partition_copy, a, a, a, odd));
 //static_assert(test(std::ranges::partition_point, a, odd));
 static_assert(test(std::ranges::pop_heap, a));
@@ -140,7 +140,7 @@ static_assert(test(std::ranges::set_union, a, a, a));
 //static_assert(test(std::ranges::shuffle, a, g));
 static_assert(test(std::ranges::sort, a));
 static_assert(test(std::ranges::sort_heap, a));
-//static_assert(test(std::ranges::stable_partition, a, odd));
+static_assert(test(std::ranges::stable_partition, a, odd));
 static_assert(test(std::ranges::stable_sort, a));
 //static_assert(test(std::ranges::starts_with, a, a));
 static_assert(test(std::ranges::swap_ranges, a, a));


        


More information about the libcxx-commits mailing list