[llvm-branch-commits] [libcxx] 1ee16f1 - [libc++][ranges] Implement `ranges::{prev, next}_permutation`.

Tom Stellard via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Aug 5 01:05:29 PDT 2022


Author: Nikolas Klauser
Date: 2022-08-05T01:04:08-07:00
New Revision: 1ee16f1049e2f79f590d6982c6a23bb4cde44a89

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

LOG: [libc++][ranges] Implement `ranges::{prev, next}_permutation`.

Co-authored-by: Konstantin Varlamov <varconst at apple.com>

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

(cherry picked from commit 68264b649461206dc095e672eacf8a003e0b9e49)

Added: 
    libcxx/include/__algorithm/ranges_next_permutation.h
    libcxx/include/__algorithm/ranges_prev_permutation.h
    libcxx/test/std/algorithms/alg.sorting/alg.permutation.generators/ranges.next_permutation.pass.cpp
    libcxx/test/std/algorithms/alg.sorting/alg.permutation.generators/ranges.prev_permutation.pass.cpp

Modified: 
    libcxx/docs/Status/RangesAlgorithms.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__algorithm/next_permutation.h
    libcxx/include/__algorithm/prev_permutation.h
    libcxx/include/__algorithm/reverse.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/ranges_result_alias_declarations.compile.pass.cpp
    libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp
    libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp
    libcxx/test/support/almost_satisfies_types.h

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/RangesAlgorithms.csv b/libcxx/docs/Status/RangesAlgorithms.csv
index 94fa26e8e0aff..b1d35131c0f89 100644
--- a/libcxx/docs/Status/RangesAlgorithms.csv
+++ b/libcxx/docs/Status/RangesAlgorithms.csv
@@ -82,8 +82,8 @@ Permutation,make_heap,Konstantin Varlamov,`D128115 <https://llvm.org/D128115>`_,
 Permutation,push_heap,Konstantin Varlamov,`D128115 <https://llvm.org/D128115>`_,✅
 Permutation,pop_heap,Konstantin Varlamov,`D128115 <https://llvm.org/D128115>`_,✅
 Permutation,sort_heap,Konstantin Varlamov,`D128115 <https://llvm.org/D128115>`_,✅
-Permutation,prev_permutation,Not assigned,n/a,Not started
-Permutation,next_permutation,Not assigned,n/a,Not started
+Permutation,next_permutation,Nikolas Klauser,`D129859 <https://llvm.org/D129859>`_,✅
+Permutation,prev_permutation,Nikolas Klauser,`D129859 <https://llvm.org/D129859>`_,✅
 Uninitialised memory,uninitialized_copy,Konstantin Varlamov,`D116023 <https://llvm.org/D116023>`_,✅
 Uninitialised memory,uninitialized_copy_n,Konstantin Varlamov,`D116023 <https://llvm.org/D116023>`_,✅
 Uninitialised memory,uninitialized_fill,Konstantin Varlamov,`D115626 <https://llvm.org/D115626>`_,✅

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 297e9029f3bf7..e4da38dfb82ba 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -113,6 +113,7 @@ set(files
   __algorithm/ranges_mismatch.h
   __algorithm/ranges_move.h
   __algorithm/ranges_move_backward.h
+  __algorithm/ranges_next_permutation.h
   __algorithm/ranges_none_of.h
   __algorithm/ranges_nth_element.h
   __algorithm/ranges_partial_sort.h
@@ -121,6 +122,7 @@ set(files
   __algorithm/ranges_partition_copy.h
   __algorithm/ranges_partition_point.h
   __algorithm/ranges_pop_heap.h
+  __algorithm/ranges_prev_permutation.h
   __algorithm/ranges_push_heap.h
   __algorithm/ranges_remove.h
   __algorithm/ranges_remove_copy.h

diff  --git a/libcxx/include/__algorithm/next_permutation.h b/libcxx/include/__algorithm/next_permutation.h
index 05e56f4a17ff6..b58dcf4e1a913 100644
--- a/libcxx/include/__algorithm/next_permutation.h
+++ b/libcxx/include/__algorithm/next_permutation.h
@@ -11,10 +11,12 @@
 
 #include <__algorithm/comp.h>
 #include <__algorithm/comp_ref_type.h>
+#include <__algorithm/iterator_operations.h>
 #include <__algorithm/reverse.h>
 #include <__config>
 #include <__iterator/iterator_traits.h>
-#include <__utility/swap.h>
+#include <__utility/move.h>
+#include <__utility/pair.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -22,29 +24,34 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _Compare, class _BidirectionalIterator>
-_LIBCPP_CONSTEXPR_AFTER_CXX17 bool
-__next_permutation(_BidirectionalIterator __first, _BidirectionalIterator __last, _Compare __comp)
+template <class _AlgPolicy, class _Compare, class _BidirectionalIterator, class _Sentinel>
+_LIBCPP_CONSTEXPR_AFTER_CXX17
+pair<_BidirectionalIterator, bool>
+__next_permutation(_BidirectionalIterator __first, _Sentinel __last, _Compare&& __comp)
 {
-    _BidirectionalIterator __i = __last;
+    using _Result = pair<_BidirectionalIterator, bool>;
+
+    _BidirectionalIterator __last_iter = _IterOps<_AlgPolicy>::next(__first, __last);
+    _BidirectionalIterator __i = __last_iter;
     if (__first == __last || __first == --__i)
-        return false;
+        return _Result(std::move(__last_iter), false);
+
     while (true)
     {
         _BidirectionalIterator __ip1 = __i;
         if (__comp(*--__i, *__ip1))
         {
-            _BidirectionalIterator __j = __last;
+            _BidirectionalIterator __j = __last_iter;
             while (!__comp(*__i, *--__j))
                 ;
-            swap(*__i, *__j);
-            _VSTD::reverse(__ip1, __last);
-            return true;
+            _IterOps<_AlgPolicy>::iter_swap(__i, __j);
+            std::__reverse<_AlgPolicy>(__ip1, __last_iter);
+            return _Result(std::move(__last_iter), true);
         }
         if (__i == __first)
         {
-            _VSTD::reverse(__first, __last);
-            return false;
+            std::__reverse<_AlgPolicy>(__first, __last_iter);
+            return _Result(std::move(__last_iter), false);
         }
     }
 }
@@ -54,8 +61,9 @@ inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
 bool
 next_permutation(_BidirectionalIterator __first, _BidirectionalIterator __last, _Compare __comp)
 {
-    typedef typename __comp_ref_type<_Compare>::type _Comp_ref;
-    return _VSTD::__next_permutation<_Comp_ref>(__first, __last, __comp);
+  using _Comp_ref = typename __comp_ref_type<_Compare>::type;
+  return std::__next_permutation<_ClassicAlgPolicy>(
+      std::move(__first), std::move(__last), static_cast<_Comp_ref>(__comp)).second;
 }
 
 template <class _BidirectionalIterator>

diff  --git a/libcxx/include/__algorithm/prev_permutation.h b/libcxx/include/__algorithm/prev_permutation.h
index 9dbc1dad01244..698506372b6f1 100644
--- a/libcxx/include/__algorithm/prev_permutation.h
+++ b/libcxx/include/__algorithm/prev_permutation.h
@@ -11,10 +11,12 @@
 
 #include <__algorithm/comp.h>
 #include <__algorithm/comp_ref_type.h>
+#include <__algorithm/iterator_operations.h>
 #include <__algorithm/reverse.h>
 #include <__config>
 #include <__iterator/iterator_traits.h>
-#include <__utility/swap.h>
+#include <__utility/move.h>
+#include <__utility/pair.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -22,29 +24,34 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _Compare, class _BidirectionalIterator>
-_LIBCPP_CONSTEXPR_AFTER_CXX17 bool
-__prev_permutation(_BidirectionalIterator __first, _BidirectionalIterator __last, _Compare __comp)
+template <class _AlgPolicy, class _Compare, class _BidirectionalIterator, class _Sentinel>
+_LIBCPP_CONSTEXPR_AFTER_CXX17
+pair<_BidirectionalIterator, bool>
+__prev_permutation(_BidirectionalIterator __first, _Sentinel __last, _Compare&& __comp)
 {
-    _BidirectionalIterator __i = __last;
+    using _Result = pair<_BidirectionalIterator, bool>;
+
+    _BidirectionalIterator __last_iter = _IterOps<_AlgPolicy>::next(__first, __last);
+    _BidirectionalIterator __i = __last_iter;
     if (__first == __last || __first == --__i)
-        return false;
+        return _Result(std::move(__last_iter), false);
+
     while (true)
     {
         _BidirectionalIterator __ip1 = __i;
         if (__comp(*__ip1, *--__i))
         {
-            _BidirectionalIterator __j = __last;
+            _BidirectionalIterator __j = __last_iter;
             while (!__comp(*--__j, *__i))
                 ;
-            swap(*__i, *__j);
-            _VSTD::reverse(__ip1, __last);
-            return true;
+            _IterOps<_AlgPolicy>::iter_swap(__i, __j);
+            std::__reverse<_AlgPolicy>(__ip1, __last_iter);
+            return _Result(std::move(__last_iter), true);
         }
         if (__i == __first)
         {
-            _VSTD::reverse(__first, __last);
-            return false;
+            std::__reverse<_AlgPolicy>(__first, __last_iter);
+            return _Result(std::move(__last_iter), false);
         }
     }
 }
@@ -54,8 +61,9 @@ inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
 bool
 prev_permutation(_BidirectionalIterator __first, _BidirectionalIterator __last, _Compare __comp)
 {
-    typedef typename __comp_ref_type<_Compare>::type _Comp_ref;
-    return _VSTD::__prev_permutation<_Comp_ref>(__first, __last, __comp);
+  using _Comp_ref = typename __comp_ref_type<_Compare>::type;
+  return std::__prev_permutation<_ClassicAlgPolicy>(
+      std::move(__first), std::move(__last), static_cast<_Comp_ref>(__comp)).second;
 }
 
 template <class _BidirectionalIterator>

diff  --git a/libcxx/include/__algorithm/ranges_next_permutation.h b/libcxx/include/__algorithm/ranges_next_permutation.h
new file mode 100644
index 0000000000000..34c5fee040500
--- /dev/null
+++ b/libcxx/include/__algorithm/ranges_next_permutation.h
@@ -0,0 +1,72 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_NEXT_PERMUTATION_H
+#define _LIBCPP___ALGORITHM_RANGES_NEXT_PERMUTATION_H
+
+#include <__algorithm/in_found_result.h>
+#include <__algorithm/iterator_operations.h>
+#include <__algorithm/make_projected.h>
+#include <__algorithm/next_permutation.h>
+#include <__config>
+#include <__functional/identity.h>
+#include <__functional/ranges_operations.h>
+#include <__iterator/concepts.h>
+#include <__iterator/sortable.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/dangling.h>
+#include <__utility/move.h>
+
+#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 _InIter>
+using next_permutation_result = in_found_result<_InIter>;
+
+namespace __next_permutation {
+
+struct __fn {
+  template <bidirectional_iterator _Iter, sentinel_for<_Iter> _Sent, class _Comp = ranges::less, class _Proj = identity>
+    requires sortable<_Iter, _Comp, _Proj>
+  _LIBCPP_HIDE_FROM_ABI constexpr next_permutation_result<_Iter>
+  operator()(_Iter __first, _Sent __last, _Comp __comp = {}, _Proj __proj = {}) const {
+    auto __result = std::__next_permutation<_RangeAlgPolicy>(
+        std::move(__first), std::move(__last), std::__make_projected(__comp, __proj));
+    return {std::move(__result.first), std::move(__result.second)};
+  }
+
+  template <bidirectional_range _Range, class _Comp = ranges::less, class _Proj = identity>
+    requires sortable<iterator_t<_Range>, _Comp, _Proj>
+  _LIBCPP_HIDE_FROM_ABI constexpr next_permutation_result<borrowed_iterator_t<_Range>>
+  operator()(_Range&& __range, _Comp __comp = {}, _Proj __proj = {}) const {
+    auto __result = std::__next_permutation<_RangeAlgPolicy>(
+        ranges::begin(__range), ranges::end(__range), std::__make_projected(__comp, __proj));
+    return {std::move(__result.first), std::move(__result.second)};
+  }
+};
+
+} // namespace __next_permutation
+
+inline namespace __cpo {
+constexpr inline auto next_permutation = __next_permutation::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+#endif // _LIBCPP___ALGORITHM_RANGES_NEXT_PERMUTATION_H

diff  --git a/libcxx/include/__algorithm/ranges_prev_permutation.h b/libcxx/include/__algorithm/ranges_prev_permutation.h
new file mode 100644
index 0000000000000..58da606d07f78
--- /dev/null
+++ b/libcxx/include/__algorithm/ranges_prev_permutation.h
@@ -0,0 +1,76 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_PREV_PERMUTATION_H
+#define _LIBCPP___ALGORITHM_RANGES_PREV_PERMUTATION_H
+
+#include <__algorithm/in_found_result.h>
+#include <__algorithm/iterator_operations.h>
+#include <__algorithm/make_projected.h>
+#include <__algorithm/prev_permutation.h>
+#include <__config>
+#include <__functional/identity.h>
+#include <__functional/ranges_operations.h>
+#include <__iterator/concepts.h>
+#include <__iterator/sortable.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/dangling.h>
+#include <__utility/move.h>
+
+#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 _InIter>
+using prev_permutation_result = in_found_result<_InIter>;
+
+namespace __prev_permutation {
+
+struct __fn {
+
+  template <bidirectional_iterator _Iter, sentinel_for<_Iter> _Sent,
+            class _Comp = ranges::less, class _Proj = identity>
+    requires sortable<_Iter, _Comp, _Proj>
+  _LIBCPP_HIDE_FROM_ABI constexpr prev_permutation_result<_Iter>
+  operator()(_Iter __first, _Sent __last, _Comp __comp = {}, _Proj __proj = {}) const {
+    auto __result = std::__prev_permutation<_RangeAlgPolicy>(
+        std::move(__first), std::move(__last), std::__make_projected(__comp, __proj));
+    return {std::move(__result.first), std::move(__result.second)};
+  }
+
+  template <bidirectional_range _Range,
+            class _Comp = ranges::less, class _Proj = identity>
+    requires sortable<iterator_t<_Range>, _Comp, _Proj>
+  _LIBCPP_HIDE_FROM_ABI constexpr prev_permutation_result<borrowed_iterator_t<_Range>>
+  operator()(_Range&& __range, _Comp __comp = {}, _Proj __proj = {}) const {
+    auto __result = std::__prev_permutation<_RangeAlgPolicy>(
+        ranges::begin(__range), ranges::end(__range), std::__make_projected(__comp, __proj));
+    return {std::move(__result.first), std::move(__result.second)};
+  }
+
+};
+
+} // namespace __prev_permutation
+
+inline namespace __cpo {
+constexpr inline auto prev_permutation = __prev_permutation::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+#endif // _LIBCPP___ALGORITHM_RANGES_PREV_PERMUTATION_H

diff  --git a/libcxx/include/__algorithm/reverse.h b/libcxx/include/__algorithm/reverse.h
index 0202cd7408332..6484c73752efb 100644
--- a/libcxx/include/__algorithm/reverse.h
+++ b/libcxx/include/__algorithm/reverse.h
@@ -10,8 +10,10 @@
 #define _LIBCPP___ALGORITHM_REVERSE_H
 
 #include <__algorithm/iter_swap.h>
+#include <__algorithm/iterator_operations.h>
 #include <__config>
 #include <__iterator/iterator_traits.h>
+#include <__utility/move.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -19,28 +21,35 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _BidirectionalIterator>
+template <class _AlgPolicy, class _BidirectionalIterator>
 inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
 void
-__reverse(_BidirectionalIterator __first, _BidirectionalIterator __last, bidirectional_iterator_tag)
+__reverse_impl(_BidirectionalIterator __first, _BidirectionalIterator __last, bidirectional_iterator_tag)
 {
     while (__first != __last)
     {
         if (__first == --__last)
             break;
-        _VSTD::iter_swap(__first, __last);
+        _IterOps<_AlgPolicy>::iter_swap(__first, __last);
         ++__first;
     }
 }
 
-template <class _RandomAccessIterator>
+template <class _AlgPolicy, class _RandomAccessIterator>
 inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
 void
-__reverse(_RandomAccessIterator __first, _RandomAccessIterator __last, random_access_iterator_tag)
+__reverse_impl(_RandomAccessIterator __first, _RandomAccessIterator __last, random_access_iterator_tag)
 {
     if (__first != __last)
         for (; __first < --__last; ++__first)
-            _VSTD::iter_swap(__first, __last);
+            _IterOps<_AlgPolicy>::iter_swap(__first, __last);
+}
+
+template <class _AlgPolicy, class _BidirectionalIterator, class _Sentinel>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX17
+void __reverse(_BidirectionalIterator __first, _Sentinel __last) {
+  using _IterCategory = typename _IterOps<_AlgPolicy>::template __iterator_category<_BidirectionalIterator>;
+  std::__reverse_impl<_AlgPolicy>(std::move(__first), std::move(__last), _IterCategory());
 }
 
 template <class _BidirectionalIterator>
@@ -48,7 +57,7 @@ inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
 void
 reverse(_BidirectionalIterator __first, _BidirectionalIterator __last)
 {
-    _VSTD::__reverse(__first, __last, typename iterator_traits<_BidirectionalIterator>::iterator_category());
+  std::__reverse<_ClassicAlgPolicy>(std::move(__first), std::move(__last));
 }
 
 _LIBCPP_END_NAMESPACE_STD

diff  --git a/libcxx/include/algorithm b/libcxx/include/algorithm
index 15ba340aa766c..6da0146447785 100644
--- a/libcxx/include/algorithm
+++ b/libcxx/include/algorithm
@@ -993,8 +993,39 @@ namespace ranges {
       replace_copy_if(R&& r, O result, Pred pred, const T& new_value,
                       Proj proj = {});                                                             // Since C++20
 
+  template<class I>
+    using prev_permutation_result = in_found_result<I>;                                            // Since C++20
+
+  template<bidirectional_iterator I, sentinel_for<I> S, class Comp = ranges::less,
+           class Proj = identity>
+    requires sortable<I, Comp, Proj>
+    constexpr ranges::prev_permutation_result<I>
+      ranges::prev_permutation(I first, S last, Comp comp = {}, Proj proj = {});                   // Since C++20
+
+  template<bidirectional_range R, class Comp = ranges::less,
+           class Proj = identity>
+    requires sortable<iterator_t<R>, Comp, Proj>
+    constexpr ranges::prev_permutation_result<borrowed_iterator_t<R>>
+      ranges::prev_permutation(R&& r, Comp comp = {}, Proj proj = {});                             // Since C++20
+
+  template<class I>
+    using next_permutation_result = in_found_result<I>;                                            // Since C++20
+
+  template<bidirectional_iterator I, sentinel_for<I> S, class Comp = ranges::less,
+           class Proj = identity>
+    requires sortable<I, Comp, Proj>
+    constexpr ranges::next_permutation_result<I>
+      ranges::next_permutation(I first, S last, Comp comp = {}, Proj proj = {});                   // Since C++20
+
+  template<bidirectional_range R, class Comp = ranges::less,
+           class Proj = identity>
+    requires sortable<iterator_t<R>, Comp, Proj>
+    constexpr ranges::next_permutation_result<borrowed_iterator_t<R>>
+      ranges::next_permutation(R&& r, Comp comp = {}, Proj proj = {});                             // Since C++20
+
 }
 
+template <class InputIterator, class Predicate>
     constexpr bool     // constexpr in C++20
     all_of(InputIterator first, InputIterator last, Predicate pred);
 
@@ -1765,6 +1796,7 @@ template <class BidirectionalIterator, class Compare>
 #include <__algorithm/ranges_mismatch.h>
 #include <__algorithm/ranges_move.h>
 #include <__algorithm/ranges_move_backward.h>
+#include <__algorithm/ranges_next_permutation.h>
 #include <__algorithm/ranges_none_of.h>
 #include <__algorithm/ranges_nth_element.h>
 #include <__algorithm/ranges_partial_sort.h>
@@ -1773,6 +1805,7 @@ template <class BidirectionalIterator, class Compare>
 #include <__algorithm/ranges_partition_copy.h>
 #include <__algorithm/ranges_partition_point.h>
 #include <__algorithm/ranges_pop_heap.h>
+#include <__algorithm/ranges_prev_permutation.h>
 #include <__algorithm/ranges_push_heap.h>
 #include <__algorithm/ranges_remove.h>
 #include <__algorithm/ranges_remove_copy.h>

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 94da66c6d0e78..bcb3a0d760e02 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -352,6 +352,7 @@ module std [system] {
       module ranges_mismatch                 { private header "__algorithm/ranges_mismatch.h" }
       module ranges_move                     { private header "__algorithm/ranges_move.h" }
       module ranges_move_backward            { private header "__algorithm/ranges_move_backward.h" }
+      module ranges_next_permutation         { private header "__algorithm/ranges_next_permutation.h" }
       module ranges_none_of                  { private header "__algorithm/ranges_none_of.h" }
       module ranges_nth_element              { private header "__algorithm/ranges_nth_element.h" }
       module ranges_partial_sort             { private header "__algorithm/ranges_partial_sort.h" }
@@ -360,6 +361,7 @@ module std [system] {
       module ranges_partition_copy           { private header "__algorithm/ranges_partition_copy.h" }
       module ranges_partition_point          { private header "__algorithm/ranges_partition_point.h" }
       module ranges_pop_heap                 { private header "__algorithm/ranges_pop_heap.h" }
+      module ranges_prev_permutation         { private header "__algorithm/ranges_prev_permutation.h" }
       module ranges_push_heap                { private header "__algorithm/ranges_push_heap.h" }
       module ranges_remove                   { private header "__algorithm/ranges_remove.h" }
       module ranges_remove_copy              { private header "__algorithm/ranges_remove_copy.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 3959d48288e20..0196235a123a9 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
@@ -165,8 +165,8 @@ constexpr bool all_the_algorithms()
     (void)std::ranges::minmax_element(a, Less(&copies)); assert(copies == 0);
     (void)std::ranges::mismatch(first, last, first2, last2, Equal(&copies)); assert(copies == 0);
     (void)std::ranges::mismatch(a, b, Equal(&copies)); assert(copies == 0);
-    //(void)std::ranges::next_permutation(first, last, Less(&copies)); assert(copies == 0);
-    //(void)std::ranges::next_permutation(a, Less(&copies)); assert(copies == 0);
+    (void)std::ranges::next_permutation(first, last, Less(&copies)); assert(copies == 0);
+    (void)std::ranges::next_permutation(a, Less(&copies)); assert(copies == 0);
     (void)std::ranges::none_of(first, last, UnaryTrue(&copies)); assert(copies == 0);
     (void)std::ranges::none_of(a, UnaryTrue(&copies)); assert(copies == 0);
     (void)std::ranges::nth_element(first, mid, last, Less(&copies)); assert(copies == 0);
@@ -183,8 +183,8 @@ constexpr bool all_the_algorithms()
     (void)std::ranges::partition_point(a, UnaryTrue(&copies)); assert(copies == 0);
     (void)std::ranges::pop_heap(first, last, Less(&copies)); assert(copies == 0);
     (void)std::ranges::pop_heap(a, Less(&copies)); assert(copies == 0);
-    //(void)std::ranges::prev_permutation(first, last, Less(&copies)); assert(copies == 0);
-    //(void)std::ranges::prev_permutation(a, Less(&copies)); assert(copies == 0);
+    (void)std::ranges::prev_permutation(first, last, Less(&copies)); assert(copies == 0);
+    (void)std::ranges::prev_permutation(a, Less(&copies)); assert(copies == 0);
     (void)std::ranges::push_heap(first, last, Less(&copies)); assert(copies == 0);
     (void)std::ranges::push_heap(a, Less(&copies)); assert(copies == 0);
     (void)std::ranges::remove_copy_if(first, last, first2, UnaryTrue(&copies)); assert(copies == 0);

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 ed6277e9eb864..aa75226d62950 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
@@ -148,8 +148,8 @@ constexpr bool all_the_algorithms()
     (void)std::ranges::minmax_element(a, Less(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::mismatch(first, last, first2, last2, Equal(), Proj(&copies), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::mismatch(a, b, Equal(), Proj(&copies), Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::next_permutation(first, last, Less(), Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::next_permutation(a, Less(), Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::next_permutation(first, last, Less(), Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::next_permutation(a, Less(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::none_of(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::none_of(a, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::nth_element(first, mid, last, Less(), Proj(&copies)); assert(copies == 0);
@@ -166,8 +166,8 @@ constexpr bool all_the_algorithms()
     (void)std::ranges::partition_point(a, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::pop_heap(first, last, Less(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::pop_heap(a, Less(), Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::prev_permutation(first, last, Less(), Proj(&copies)); assert(copies == 0);
-    //(void)std::ranges::prev_permutation(a, Less(), Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::prev_permutation(first, last, Less(), Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::prev_permutation(a, Less(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::push_heap(first, last, Less(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::push_heap(a, Less(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::remove_copy(first, last, first2, value, Proj(&copies)); assert(copies == 0);

diff  --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp
index d43bf1302cba8..59abd9109ec0a 100644
--- a/libcxx/test/libcxx/private_headers.verify.cpp
+++ b/libcxx/test/libcxx/private_headers.verify.cpp
@@ -150,6 +150,7 @@ END-SCRIPT
 #include <__algorithm/ranges_mismatch.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_mismatch.h'}}
 #include <__algorithm/ranges_move.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_move.h'}}
 #include <__algorithm/ranges_move_backward.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_move_backward.h'}}
+#include <__algorithm/ranges_next_permutation.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_next_permutation.h'}}
 #include <__algorithm/ranges_none_of.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_none_of.h'}}
 #include <__algorithm/ranges_nth_element.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_nth_element.h'}}
 #include <__algorithm/ranges_partial_sort.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_partial_sort.h'}}
@@ -158,6 +159,7 @@ END-SCRIPT
 #include <__algorithm/ranges_partition_copy.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_partition_copy.h'}}
 #include <__algorithm/ranges_partition_point.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_partition_point.h'}}
 #include <__algorithm/ranges_pop_heap.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_pop_heap.h'}}
+#include <__algorithm/ranges_prev_permutation.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_prev_permutation.h'}}
 #include <__algorithm/ranges_push_heap.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_push_heap.h'}}
 #include <__algorithm/ranges_remove.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_remove.h'}}
 #include <__algorithm/ranges_remove_copy.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_remove_copy.h'}}

diff  --git a/libcxx/test/std/algorithms/alg.sorting/alg.permutation.generators/ranges.next_permutation.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.permutation.generators/ranges.next_permutation.pass.cpp
new file mode 100644
index 0000000000000..5f412eced4a30
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.permutation.generators/ranges.next_permutation.pass.cpp
@@ -0,0 +1,272 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<bidirectional_iterator I, sentinel_for<I> S, class Comp = ranges::less,
+//          class Proj = identity>
+//   requires sortable<I, Comp, Proj>
+//   constexpr ranges::next_permutation_result<I>
+//     ranges::next_permutation(I first, S last, Comp comp = {}, Proj proj = {});
+// template<bidirectional_range R, class Comp = ranges::less,
+//          class Proj = identity>
+//   requires sortable<iterator_t<R>, Comp, Proj>
+//   constexpr ranges::next_permutation_result<borrowed_iterator_t<R>>
+//     ranges::next_permutation(R&& r, Comp comp = {}, Proj proj = {});
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <ranges>
+
+#include "almost_satisfies_types.h"
+#include "test_iterators.h"
+
+template <class Iter, class Sent = sentinel_wrapper<Iter>>
+concept HasNextPermutationIt = requires(Iter first, Sent last) { std::ranges::next_permutation(first, last); };
+
+static_assert(HasNextPermutationIt<int*>);
+static_assert(!HasNextPermutationIt<BidirectionalIteratorNotDerivedFrom>);
+static_assert(!HasNextPermutationIt<BidirectionalIteratorNotDecrementable>);
+static_assert(!HasNextPermutationIt<int*, SentinelForNotSemiregular>);
+static_assert(!HasNextPermutationIt<int*, SentinelForNotWeaklyEqualityComparableWith>);
+static_assert(!HasNextPermutationIt<const int*>); // not sortable
+
+template <class Range>
+concept HasNextPermutationR = requires(Range range) { std::ranges::next_permutation(range); };
+
+static_assert(HasNextPermutationR<UncheckedRange<int*>>);
+static_assert(!HasNextPermutationR<BidirectionalRangeNotDerivedFrom>);
+static_assert(!HasNextPermutationR<BidirectionalRangeNotDecrementable>);
+static_assert(!HasNextPermutationR<BidirectionalRangeNotSentinelSemiregular>);
+static_assert(!HasNextPermutationR<BidirectionalRangeNotSentinelWeaklyEqualityComparableWith>);
+static_assert(!HasNextPermutationR<UncheckedRange<const int*>>); // not sortable
+
+constexpr size_t factorial(size_t i) {
+  std::array memoized = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
+  return memoized[i];
+}
+
+template <class Iter, class Range, class Func>
+constexpr bool run_next_permutation(Func call_next_permutation, Range permuted, Range previous) {
+  using Result = std::ranges::next_permutation_result<Iter>;
+
+  std::same_as<Result> decltype(auto) ret = call_next_permutation(permuted);
+  assert(ret.in == permuted.end());
+  bool next_found = ret.found;
+
+  if (std::ranges::distance(permuted) > 1) {
+    if (next_found) {
+      assert(std::ranges::lexicographical_compare(previous, permuted));
+    } else {
+      assert(std::ranges::lexicographical_compare(permuted, previous));
+      assert(std::ranges::is_sorted(permuted));
+    }
+  }
+
+  return next_found;
+}
+
+template <class Iter, class Sent, class Func>
+constexpr void test_next_permutations(Func call_next_permutation) {
+  std::array input = {1, 2, 3, 4};
+  auto current_permutation = input;
+  auto previous_permutation = current_permutation;
+
+  // For all subarrays of `input` from `[0, 0]` to `[0, N - 1]`, call `next_permutation` until no next permutation
+  // exists.
+  // The number of permutations must equal `N!`. `run_next_permutation` checks that each next permutation is
+  // lexicographically greater than the previous. If these two conditions hold (the number of permutations is `N!`, and
+  // each permutation is lexicographically greater than the previous one), it follows that the
+  // `ranges::next_permutation` algorithm works correctly.
+  for (size_t i = 0; i <= current_permutation.size(); ++i) {
+    size_t count = 0;
+    bool next_found = true;
+
+    while (next_found) {
+      ++count;
+      previous_permutation = current_permutation;
+
+      auto current_subrange = std::ranges::subrange(
+          Iter(current_permutation.data()), Sent(Iter(current_permutation.data() + i)));
+      auto previous_subrange = std::ranges::subrange(
+          Iter(previous_permutation.data()), Sent(Iter(previous_permutation.data() + i)));
+
+      next_found = run_next_permutation<Iter>(call_next_permutation, current_subrange, previous_subrange);
+    }
+
+    assert(count == factorial(i));
+  }
+}
+
+template <class Iter, class Sent>
+constexpr void test_all_permutations() {
+  test_next_permutations<Iter, Sent>([](auto&& range) {
+    return std::ranges::next_permutation(range.begin(), range.end());
+  });
+
+  test_next_permutations<Iter, Sent>([](auto&& range) {
+    return std::ranges::next_permutation(range);
+  });
+}
+
+template <class Iter, class Sent, int N>
+constexpr void test_one(const std::array<int, N> input, bool expected_found, std::array<int, N> expected) {
+  using Result = std::ranges::next_permutation_result<Iter>;
+
+  { // (iterator, sentinel) overload.
+    auto in = input;
+    auto begin = Iter(in.data());
+    auto end = Sent(Iter(in.data() + in.size()));
+
+    std::same_as<Result> decltype(auto) result = std::ranges::next_permutation(begin, end);
+    assert(result.found == expected_found);
+    assert(result.in == end);
+    assert(in == expected);
+  }
+
+  {  // (range) overload.
+    auto in = input;
+    auto begin = Iter(in.data());
+    auto end = Sent(Iter(in.data() + in.size()));
+    auto range = std::ranges::subrange(begin, end);
+
+    std::same_as<Result> decltype(auto) result = std::ranges::next_permutation(range);
+    assert(result.found == expected_found);
+    assert(result.in == end);
+    assert(in == expected);
+  }
+}
+
+template <class Iter, class Sent>
+constexpr void test_iter_sent() {
+  test_all_permutations<Iter, Sent>();
+
+  // Empty range.
+  test_one<Iter, Sent, 0>({}, false, {});
+  // 1-element range.
+  test_one<Iter, Sent, 1>({1}, false, {1});
+  // 2-element range.
+  test_one<Iter, Sent, 2>({1, 2}, true, {2, 1});
+  test_one<Iter, Sent, 2>({2, 1}, false, {1, 2});
+  // Longer sequence.
+  test_one<Iter, Sent, 8>({1, 2, 3, 4, 5, 6, 7, 8}, true, {1, 2, 3, 4, 5, 6, 8, 7});
+  // Longer sequence, permutations exhausted.
+  test_one<Iter, Sent, 8>({8, 7, 6, 5, 4, 3, 2, 1}, false, {1, 2, 3, 4, 5, 6, 7, 8});
+}
+
+template <class Iter>
+constexpr void test_iter() {
+  test_iter_sent<Iter, Iter>();
+  test_iter_sent<Iter, sentinel_wrapper<Iter>>();
+  test_iter_sent<Iter, sized_sentinel<Iter>>();
+}
+
+constexpr void test_iterators() {
+  test_iter<bidirectional_iterator<int*>>();
+  test_iter<random_access_iterator<int*>>();
+  test_iter<contiguous_iterator<int*>>();
+  test_iter<int*>();
+}
+
+constexpr bool test() {
+  test_iterators();
+
+  { // A custom predicate works.
+    struct A {
+      int i;
+      constexpr bool comp(const A& rhs) const { return i > rhs.i; }
+      constexpr bool operator==(const A&) const = default;
+    };
+    const std::array input = {A{1}, A{2}, A{3}, A{4}, A{5}};
+    std::array expected = {A{5}, A{4}, A{3}, A{2}, A{1}};
+
+    { // (iterator, sentinel) overload.
+      auto in = input;
+      auto result = std::ranges::next_permutation(in.begin(), in.end(), &A::comp);
+
+      assert(result.found == false);
+      assert(result.in == in.end());
+      assert(in == expected);
+    }
+
+    { // (range) overload.
+      auto in = input;
+      auto result = std::ranges::next_permutation(in, &A::comp);
+
+      assert(result.found == false);
+      assert(result.in == in.end());
+      assert(in == expected);
+    }
+  }
+
+  { // A custom projection works.
+    struct A {
+      int i;
+      constexpr A negate() const { return A{i * -1}; }
+      constexpr auto operator<=>(const A&) const = default;
+    };
+    const std::array input = {A{1}, A{2}, A{3}, A{4}, A{5}};
+    std::array expected = {A{5}, A{4}, A{3}, A{2}, A{1}};
+
+    { // (iterator, sentinel) overload.
+      auto in = input;
+      auto result = std::ranges::next_permutation(in.begin(), in.end(), {}, &A::negate);
+
+      assert(result.found == false);
+      assert(result.in == in.end());
+      assert(in == expected);
+    }
+
+    { // (range) overload.
+      auto in = input;
+      auto result = std::ranges::next_permutation(in, {}, &A::negate);
+
+      assert(result.found == false);
+      assert(result.in == in.end());
+      assert(in == expected);
+    }
+  }
+
+  { // Complexity: At most `(last - first) / 2` swaps.
+    const std::array input = {1, 2, 3, 4, 5, 6};
+
+    { // (iterator, sentinel) overload.
+      auto in = input;
+      int swaps_count = 0;
+      auto begin = adl::Iterator::TrackSwaps(in.data(), swaps_count);
+      auto end = adl::Iterator::TrackSwaps(in.data() + in.size(), swaps_count);
+
+      std::ranges::next_permutation(begin, end);
+      assert(swaps_count <= (base(end) - base(begin) + 1) / 2);
+    }
+
+    { // (range) overload.
+      auto in = input;
+      int swaps_count = 0;
+      auto begin = adl::Iterator::TrackSwaps(in.data(), swaps_count);
+      auto end = adl::Iterator::TrackSwaps(in.data() + in.size(), swaps_count);
+
+      std::ranges::next_permutation(std::ranges::subrange(begin, end));
+      assert(swaps_count <= (base(end) - base(begin) + 1) / 2);
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/algorithms/alg.sorting/alg.permutation.generators/ranges.prev_permutation.pass.cpp b/libcxx/test/std/algorithms/alg.sorting/alg.permutation.generators/ranges.prev_permutation.pass.cpp
new file mode 100644
index 0000000000000..17db20279a3b7
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.sorting/alg.permutation.generators/ranges.prev_permutation.pass.cpp
@@ -0,0 +1,272 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template<bidirectional_iterator I, sentinel_for<I> S, class Comp = ranges::less,
+//          class Proj = identity>
+//   requires sortable<I, Comp, Proj>
+//   constexpr ranges::prev_permutation_result<I>
+//     ranges::prev_permutation(I first, S last, Comp comp = {}, Proj proj = {});
+// template<bidirectional_range R, class Comp = ranges::less,
+//          class Proj = identity>
+//   requires sortable<iterator_t<R>, Comp, Proj>
+//   constexpr ranges::prev_permutation_result<borrowed_iterator_t<R>>
+//     ranges::prev_permutation(R&& r, Comp comp = {}, Proj proj = {});
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <cstddef>
+#include <ranges>
+
+#include "almost_satisfies_types.h"
+#include "test_iterators.h"
+
+template <class Iter, class Sent = sentinel_wrapper<Iter>>
+concept HasPrevPermutationIt = requires(Iter first, Sent last) { std::ranges::prev_permutation(first, last); };
+
+static_assert(HasPrevPermutationIt<int*>);
+static_assert(!HasPrevPermutationIt<BidirectionalIteratorNotDerivedFrom>);
+static_assert(!HasPrevPermutationIt<BidirectionalIteratorNotDecrementable>);
+static_assert(!HasPrevPermutationIt<int*, SentinelForNotSemiregular>);
+static_assert(!HasPrevPermutationIt<int*, SentinelForNotWeaklyEqualityComparableWith>);
+static_assert(!HasPrevPermutationIt<const int*>); // not sortable
+
+template <class Range>
+concept HasPrevPermutationR = requires(Range range) { std::ranges::prev_permutation(range); };
+
+static_assert(HasPrevPermutationR<UncheckedRange<int*>>);
+static_assert(!HasPrevPermutationR<BidirectionalRangeNotDerivedFrom>);
+static_assert(!HasPrevPermutationR<BidirectionalRangeNotDecrementable>);
+static_assert(!HasPrevPermutationR<BidirectionalRangeNotSentinelSemiregular>);
+static_assert(!HasPrevPermutationR<BidirectionalRangeNotSentinelWeaklyEqualityComparableWith>);
+static_assert(!HasPrevPermutationR<UncheckedRange<const int*>>); // not sortable
+
+constexpr size_t factorial(size_t i) {
+  std::array memoized = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
+  return memoized[i];
+}
+
+template <class Iter, class Range, class Func>
+constexpr bool run_prev_permutation(Func call_prev_permutation, Range permuted, Range previous) {
+  using Result = std::ranges::prev_permutation_result<Iter>;
+
+  std::same_as<Result> decltype(auto) ret = call_prev_permutation(permuted);
+  assert(ret.in == permuted.end());
+  bool next_found = ret.found;
+
+  if (std::ranges::distance(permuted) > 1) {
+    if (next_found) {
+      assert(std::ranges::lexicographical_compare(permuted, previous));
+    } else {
+      assert(std::ranges::lexicographical_compare(previous, permuted));
+      assert(std::ranges::is_sorted(permuted, std::ranges::greater{}));
+    }
+  }
+
+  return next_found;
+}
+
+template <class Iter, class Sent, class Func>
+constexpr void test_prev_permutations(Func call_prev_permutation) {
+  std::array input = {4, 3, 2, 1};
+  auto current_permutation = input;
+  auto previous_permutation = current_permutation;
+
+  // For all subarrays of `input` from `[0, 0]` to `[0, N - 1]`, call `prev_permutation` until no previous permutation
+  // exists.
+  // The number of permutations must equal `N!`. `run_prev_permutation` checks that each previous permutation is
+  // lexicographically less than the previous. If these two conditions hold (the number of permutations is `N!`, and
+  // each permutation is lexicographically less than the previous one), it follows that the `ranges::prev_permutation`
+  // algorithm works correctly.
+  for (size_t i = 0; i <= current_permutation.size(); ++i) {
+    size_t count = 0;
+    bool next_found = true;
+
+    while (next_found) {
+      ++count;
+      previous_permutation = current_permutation;
+
+      auto current_subrange = std::ranges::subrange(
+          Iter(current_permutation.data()), Sent(Iter(current_permutation.data() + i)));
+      auto previous_subrange = std::ranges::subrange(
+          Iter(previous_permutation.data()), Sent(Iter(previous_permutation.data() + i)));
+
+      next_found = run_prev_permutation<Iter>(call_prev_permutation, current_subrange, previous_subrange);
+    }
+
+    assert(count == factorial(i));
+  }
+}
+
+template <class Iter, class Sent>
+constexpr void test_all_permutations() {
+  test_prev_permutations<Iter, Sent>([](auto&& range) {
+    return std::ranges::prev_permutation(range.begin(), range.end());
+  });
+
+  test_prev_permutations<Iter, Sent>([](auto&& range) {
+    return std::ranges::prev_permutation(range);
+  });
+}
+
+template <class Iter, class Sent, int N>
+constexpr void test_one(const std::array<int, N> input, bool expected_found, std::array<int, N> expected) {
+  using Result = std::ranges::prev_permutation_result<Iter>;
+
+  { // (iterator, sentinel) overload.
+    auto in = input;
+    auto begin = Iter(in.data());
+    auto end = Sent(Iter(in.data() + in.size()));
+
+    std::same_as<Result> decltype(auto) result = std::ranges::prev_permutation(begin, end);
+    assert(result.found == expected_found);
+    assert(result.in == end);
+    assert(in == expected);
+  }
+
+  {  // (range) overload.
+    auto in = input;
+    auto begin = Iter(in.data());
+    auto end = Sent(Iter(in.data() + in.size()));
+    auto range = std::ranges::subrange(begin, end);
+
+    std::same_as<Result> decltype(auto) result = std::ranges::prev_permutation(range);
+    assert(result.found == expected_found);
+    assert(result.in == end);
+    assert(in == expected);
+  }
+}
+
+template <class Iter, class Sent>
+constexpr void test_iter_sent() {
+  test_all_permutations<Iter, Sent>();
+
+  // Empty range.
+  test_one<Iter, Sent, 0>({}, false, {});
+  // 1-element range.
+  test_one<Iter, Sent, 1>({1}, false, {1});
+  // 2-element range.
+  test_one<Iter, Sent, 2>({1, 2}, false, {2, 1});
+  test_one<Iter, Sent, 2>({2, 1}, true, {1, 2});
+  // Longer sequence.
+  test_one<Iter, Sent, 8>({8, 7, 6, 5, 4, 3, 2, 1}, true, {8, 7, 6, 5, 4, 3, 1, 2});
+  // Longer sequence, permutations exhausted.
+  test_one<Iter, Sent, 8>({1, 2, 3, 4, 5, 6, 7, 8}, false, {8, 7, 6, 5, 4, 3, 2, 1});
+}
+
+template <class Iter>
+constexpr void test_iter() {
+  test_iter_sent<Iter, Iter>();
+  test_iter_sent<Iter, sentinel_wrapper<Iter>>();
+  test_iter_sent<Iter, sized_sentinel<Iter>>();
+}
+
+constexpr void test_iterators() {
+  test_iter<bidirectional_iterator<int*>>();
+  test_iter<random_access_iterator<int*>>();
+  test_iter<contiguous_iterator<int*>>();
+  test_iter<int*>();
+}
+
+constexpr bool test() {
+  test_iterators();
+
+  { // A custom predicate works.
+    struct A {
+      int i;
+      constexpr bool comp(const A& rhs) const { return i > rhs.i; }
+      constexpr bool operator==(const A&) const = default;
+    };
+    const std::array input = {A{5}, A{4}, A{3}, A{2}, A{1}};
+    std::array expected = {A{1}, A{2}, A{3}, A{4}, A{5}};
+
+    { // (iterator, sentinel) overload.
+      auto in = input;
+      auto result = std::ranges::prev_permutation(in.begin(), in.end(), &A::comp);
+
+      assert(result.found == false);
+      assert(result.in == in.end());
+      assert(in == expected);
+    }
+
+    { // (range) overload.
+      auto in = input;
+      auto result = std::ranges::prev_permutation(in, &A::comp);
+
+      assert(result.found == false);
+      assert(result.in == in.end());
+      assert(in == expected);
+    }
+  }
+
+  { // A custom projection works.
+    struct A {
+      int i;
+      constexpr A negate() const { return A{i * -1}; }
+      constexpr auto operator<=>(const A&) const = default;
+    };
+    const std::array input = {A{5}, A{4}, A{3}, A{2}, A{1}};
+    std::array expected = {A{1}, A{2}, A{3}, A{4}, A{5}};
+
+    { // (iterator, sentinel) overload.
+      auto in = input;
+      auto result = std::ranges::prev_permutation(in.begin(), in.end(), {}, &A::negate);
+
+      assert(result.found == false);
+      assert(result.in == in.end());
+      assert(in == expected);
+    }
+
+    { // (range) overload.
+      auto in = input;
+      auto result = std::ranges::prev_permutation(in, {}, &A::negate);
+
+      assert(result.found == false);
+      assert(result.in == in.end());
+      assert(in == expected);
+    }
+  }
+
+  { // Complexity: At most `(last - first) / 2` swaps.
+    const std::array input = {6, 5, 4, 3, 2, 1};
+
+    { // (iterator, sentinel) overload.
+      auto in = input;
+      int swaps_count = 0;
+      auto begin = adl::Iterator::TrackSwaps(in.data(), swaps_count);
+      auto end = adl::Iterator::TrackSwaps(in.data() + in.size(), swaps_count);
+
+      std::ranges::prev_permutation(begin, end);
+      assert(swaps_count <= (base(end) - base(begin) + 1) / 2);
+    }
+
+    { // (range) overload.
+      auto in = input;
+      int swaps_count = 0;
+      auto begin = adl::Iterator::TrackSwaps(in.data(), swaps_count);
+      auto end = adl::Iterator::TrackSwaps(in.data() + in.size(), swaps_count);
+
+      std::ranges::prev_permutation(std::ranges::subrange(begin, end));
+      assert(swaps_count <= (base(end) - base(begin) + 1) / 2);
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp b/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp
index 5c8a1031997a0..953fc72f18dec 100644
--- a/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp
+++ b/libcxx/test/std/algorithms/ranges_result_alias_declarations.compile.pass.cpp
@@ -57,7 +57,7 @@ static_assert(std::is_same_v<in_out_out_result<int, long, char>, partition_copy_
 static_assert(std::is_same_v<min_max_result<int>, minmax_result<int>>);
 static_assert(std::is_same_v<min_max_result<int>, minmax_element_result<int>>);
 
-// static_assert(std::is_same_v<in_found_result<int>, next_permutation_result<int>>);
-// static_assert(std::is_same_v<in_found_result<int>, prev_permutation_result<int>>);
+static_assert(std::is_same_v<in_found_result<int>, next_permutation_result<int>>);
+static_assert(std::is_same_v<in_found_result<int>, prev_permutation_result<int>>);
 
 // static_assert(std::is_same_v<out_value_result<int>, iota_result<int>>);

diff  --git a/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp
index 9ad1273880e18..222b45fd2e27a 100644
--- a/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp
+++ b/libcxx/test/std/algorithms/ranges_robust_against_nonbool_predicates.pass.cpp
@@ -139,8 +139,8 @@ constexpr bool test_all() {
   test(std::ranges::push_heap, in, binary_pred);
   test(std::ranges::pop_heap, in, binary_pred);
   test(std::ranges::sort_heap, in, binary_pred);
-  //test(std::ranges::prev_permutation, in, binary_pred);
-  //test(std::ranges::next_permutation, in, binary_pred);
+  test(std::ranges::prev_permutation, in, binary_pred);
+  test(std::ranges::next_permutation, in, binary_pred);
 
   return true;
 }

diff  --git a/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp
index 013b6d7984dde..a82002659a29d 100644
--- a/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp
+++ b/libcxx/test/std/algorithms/ranges_robust_against_omitting_invoke.pass.cpp
@@ -168,8 +168,8 @@ constexpr bool test_all() {
   test(std::ranges::push_heap, in, &Foo::binary_pred, &Bar::val);
   test(std::ranges::pop_heap, in, &Foo::binary_pred, &Bar::val);
   test(std::ranges::sort_heap, in, &Foo::binary_pred, &Bar::val);
-  //test(std::ranges::prev_permutation, in, &Foo::binary_pred, &Bar::val);
-  //test(std::ranges::next_permutation, in, &Foo::binary_pred, &Bar::val);
+  test(std::ranges::prev_permutation, in, &Foo::binary_pred, &Bar::val);
+  test(std::ranges::next_permutation, in, &Foo::binary_pred, &Bar::val);
 
   return true;
 }

diff  --git a/libcxx/test/support/almost_satisfies_types.h b/libcxx/test/support/almost_satisfies_types.h
index 592eec5da4424..2a60c22ac75a8 100644
--- a/libcxx/test/support/almost_satisfies_types.h
+++ b/libcxx/test/support/almost_satisfies_types.h
@@ -202,6 +202,10 @@ class BidirectionalIteratorNotDerivedFrom {
 };
 
 using BidirectionalRangeNotDerivedFrom = UncheckedRange<BidirectionalIteratorNotDerivedFrom>;
+using BidirectionalRangeNotSentinelSemiregular =
+    UncheckedRange<bidirectional_iterator<int*>, SentinelForNotSemiregular>;
+using BidirectionalRangeNotSentinelWeaklyEqualityComparableWith =
+    UncheckedRange<bidirectional_iterator<int*>, SentinelForNotWeaklyEqualityComparableWith>;
 
 static_assert(std::forward_iterator<BidirectionalIteratorNotDerivedFrom>);
 static_assert(!std::bidirectional_iterator<BidirectionalIteratorNotDerivedFrom>);


        


More information about the llvm-branch-commits mailing list