Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libcxx] patch for implementing ranges::find_last #67270

Closed
wants to merge 6 commits into from

Conversation

phyBrackets
Copy link
Member

Follow up the discussion from https://reviews.llvm.org/D136522

@phyBrackets phyBrackets requested a review from a team as a code owner September 24, 2023 18:53
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Sep 24, 2023
@llvmbot
Copy link
Collaborator

llvmbot commented Sep 24, 2023

@llvm/pr-subscribers-libcxx

Changes

Follow up the discussion from https://reviews.llvm.org/D136522


Patch is 59.02 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/67270.diff

20 Files Affected:

  • (modified) libcxx/docs/FeatureTestMacroTable.rst (+2)
  • (modified) libcxx/docs/ReleaseNotes/18.rst (+1-1)
  • (modified) libcxx/docs/Status/Cxx23Papers.csv (+1-1)
  • (modified) libcxx/include/CMakeLists.txt (+3)
  • (added) libcxx/include/__algorithm/ranges_find_last.h (+62)
  • (added) libcxx/include/__algorithm/ranges_find_last_if.h (+77)
  • (added) libcxx/include/__algorithm/ranges_find_last_if_not.h (+69)
  • (modified) libcxx/include/algorithm (+33)
  • (modified) libcxx/include/module.modulemap.in (+12)
  • (modified) libcxx/include/version (+2)
  • (modified) libcxx/test/libcxx/algorithms/ranges_robust_against_copying_comparators.pass.cpp (+4)
  • (modified) libcxx/test/libcxx/algorithms/ranges_robust_against_copying_projections.pass.cpp (+6)
  • (modified) libcxx/test/libcxx/diagnostics/ranges.nodiscard_extensions.verify.cpp (+4)
  • (added) libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last.pass.cpp (+290)
  • (added) libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if.pass.cpp (+364)
  • (added) libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if_not.pass.cpp (+327)
  • (modified) libcxx/test/std/language.support/support.limits/support.limits.general/algorithm.version.compile.pass.cpp (+1)
  • (modified) libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp (+3)
  • (modified) libcxx/test/support/almost_satisfies_types.h (+9)
  • (modified) libcxx/utils/generate_feature_test_macro_components.py (+5)
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index e1b4172b22c53da..e2224a5a0ce5cc6 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -322,6 +322,8 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_expected``                              ``202211L``
     --------------------------------------------------- -----------------
+    ``__cpp_lib_find_last``                             ``202207L``
+    ------------------------------------------------- -------------------
     ``__cpp_lib_format_ranges``                         ``202207L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_formatters``                            *unimplemented*
diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index 8869d0b07d39f74..d6a57d8cac91a99 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -52,7 +52,7 @@ Implemented Papers
 - P2697R1 - Interfacing ``bitset`` with ``string_view``
 - P2443R1 - ``views::chunk_by``
 - P2538R1 - ADL-proof ``std::projected``
-
+- P1223R5 - ``find_last``
 
 Improvements and New Features
 -----------------------------
diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index 9cb49fd5176ea5f..216aca3a5f2b3c8 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -55,7 +55,7 @@
 "`P0429R9 <https://wg21.link/P0429R9>`__","LWG","A Standard ``flat_map``","July 2022","",""
 "`P1169R4 <https://wg21.link/P1169R4>`__","LWG","``static operator()``","July 2022","|Complete|","16.0"
 "`P1222R4 <https://wg21.link/P1222R4>`__","LWG","A Standard ``flat_set``","July 2022","",""
-"`P1223R5 <https://wg21.link/P1223R5>`__","LWG","``ranges::find_last()``, ``ranges::find_last_if()``, and ``ranges::find_last_if_not()``","July 2022","","","|ranges|"
+"`P1223R5 <https://wg21.link/P1223R5>`__","LWG","``ranges::find_last()``, ``ranges::find_last_if()``, and ``ranges::find_last_if_not()``","July 2022","|Complete|","18.0","|ranges|"
 "`P1467R9 <https://wg21.link/P1467R9>`__","LWG","Extended ``floating-point`` types and standard names","July 2022","",""
 "`P1642R11 <https://wg21.link/P1642R11>`__","LWG","Freestanding ``[utilities]``, ``[ranges]``, and ``[iterators]``","July 2022","",""
 "`P1899R3 <https://wg21.link/P1899R3>`__","LWG","``stride_view``","July 2022","","","|ranges|"
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 2ec755236dbaee2..45083070acd4751 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -120,6 +120,9 @@ set(files
   __algorithm/ranges_find_first_of.h
   __algorithm/ranges_find_if.h
   __algorithm/ranges_find_if_not.h
+  __algorithm/ranges_find_last.h
+  __algorithm/ranges_find_last_if.h
+  __algorithm/ranges_find_last_if_not.h
   __algorithm/ranges_for_each.h
   __algorithm/ranges_for_each_n.h
   __algorithm/ranges_generate.h
diff --git a/libcxx/include/__algorithm/ranges_find_last.h b/libcxx/include/__algorithm/ranges_find_last.h
new file mode 100644
index 000000000000000..81c2caef413bbab
--- /dev/null
+++ b/libcxx/include/__algorithm/ranges_find_last.h
@@ -0,0 +1,62 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_FIND_LAST_H
+#define _LIBCPP___ALGORITHM_RANGES_FIND_LAST_H
+
+#include <__algorithm/ranges_find_last_if.h>
+#include <__config>
+#include <__functional/identity.h>
+#include <__iterator/concepts.h>
+#include <__functional/ranges_operations.h>
+#include <__iterator/projected.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/dangling.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 23
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace ranges {
+namespace __find_last {
+struct __fn {
+  template <forward_iterator _Ip, sentinel_for<_Ip> _Sp, class _Tp, class _Proj = identity>
+    requires indirect_binary_predicate <ranges::equal_to, projected<_Ip, _Proj>, const _Tp*>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr subrange<_Ip>
+               operator()(_Ip __first, _Sp __last, const _Tp& __value, _Proj __proj = {}) const {
+    auto __pred = [&](auto&& __e) { return std::forward<decltype(__e)>(__e) == __value; };
+    return ranges::__find_last_if_impl(std::move(__first), std::move(__last), __pred, __proj);
+  }
+
+  template <forward_range _Rp, class _Tp, class _Proj = identity>
+    requires indirect_binary_predicate <ranges::equal_to, projected<iterator_t<_Rp>, _Proj>, const _Tp*> 
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr borrowed_subrange_t<_Rp>
+               operator()(_Rp&& __r, const _Tp& __value, _Proj __proj = {}) const {
+    auto __pred = [&](auto&& __e) { return std::forward<decltype(__e)>(__e) == __value; };
+    return ranges::__find_last_if_impl(ranges::begin(__r), ranges::end(__r), __pred, __proj);
+  }
+};
+} // namespace __find_last
+
+inline namespace __cpo {
+inline constexpr auto find_last = __find_last::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 23
+
+#endif // _LIBCPP___ALGORITHM_RANGES_FIND_LAST_H
diff --git a/libcxx/include/__algorithm/ranges_find_last_if.h b/libcxx/include/__algorithm/ranges_find_last_if.h
new file mode 100644
index 000000000000000..894144fd6f2ea8e
--- /dev/null
+++ b/libcxx/include/__algorithm/ranges_find_last_if.h
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_FIND_LAST_IF_H
+#define _LIBCPP___ALGORITHM_RANGES_FIND_LAST_IF_H
+
+#include <__config>
+#include <__functional/identity.h>
+#include <__functional/invoke.h>
+#include <__iterator/concepts.h>
+#include <__iterator/projected.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/dangling.h>
+#include <__ranges/range_adaptor.h>
+#include <__ranges/subrange.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 23
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace ranges {
+
+template <class _Ip, class _Sp, class _Pred, class _Proj>
+_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI static constexpr subrange<_Ip>
+__find_last_if_impl(_Ip __first, _Sp __last, _Pred __pred, _Proj __proj) {
+  _Ip __result = __last;
+  while (__first != __last) {
+    if (std::invoke(__pred, std::invoke(__proj, *__first))) {
+      __result = __first;
+    }
+    ++__first;
+  }
+  return {__result, __last};
+}
+
+namespace __find_last_if {
+struct __fn {
+  template <forward_range _Rp,
+            class _Proj = identity,
+            indirect_unary_predicate<projected<iterator_t<_Rp>, _Proj>> _Pred>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr borrowed_subrange_t<_Rp>
+  operator()(_Rp&& __r, _Pred __pred, _Proj __proj = {}) const {
+    return ranges::__find_last_if_impl(ranges::begin(__r), ranges::end(__r), __pred, __proj);
+  }
+
+  template <forward_iterator _Ip,
+            sentinel_for<_Ip> _Sp,
+            class _Proj = identity,
+            indirect_unary_predicate<projected<_Ip, _Proj>> _Pred>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr subrange<_Ip>
+  operator()(_Ip __first, _Sp __last, _Pred __pred, _Proj __proj = {}) const {
+    return ranges::__find_last_if_impl(std::move(__first), std::move(__last), __pred, __proj);
+  }
+};
+} // namespace __find_last_if
+
+inline namespace __cpo {
+inline constexpr auto find_last_if = __find_last_if::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 23
+
+#endif // _LIBCPP___ALGORITHM_RANGES_FIND_LAST_IF_H
diff --git a/libcxx/include/__algorithm/ranges_find_last_if_not.h b/libcxx/include/__algorithm/ranges_find_last_if_not.h
new file mode 100644
index 000000000000000..e1b29d8f4870e1d
--- /dev/null
+++ b/libcxx/include/__algorithm/ranges_find_last_if_not.h
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_FIND_LAST_IF_NOT_H
+#define _LIBCPP___ALGORITHM_RANGES_FIND_LAST_IF_NOT_H
+
+#include <__algorithm/ranges_find_last_if.h>
+#include <__config>
+#include <__functional/identity.h>
+#include <__functional/invoke.h>
+#include <__iterator/concepts.h>
+#include <__iterator/projected.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/dangling.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 23
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace ranges {
+namespace __find_last_if_not {
+struct __fn {
+  static constexpr auto __make_negate_pred = [](auto&& __pred) {
+    return [&__pred](auto&& __e) { return !std::invoke(__pred, std::forward<decltype(__e)>(__e)); };
+};
+
+  template <forward_iterator _Ip,
+            sentinel_for<_Ip> _Sp,
+            class _Proj = identity,
+            indirect_unary_predicate<projected<_Ip, _Proj>> _Pred>
+ _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr subrange<_Ip>
+  operator()(_Ip __first, _Sp __last, _Pred __pred, _Proj __proj = {}) const {
+    auto __negate_pred = __make_negate_pred(__pred);
+    return ranges::__find_last_if_impl(std::move(__first), std::move(__last), __negate_pred, __proj);
+  }
+
+  template <forward_range _Rp,
+            class _Proj = identity,
+            indirect_unary_predicate<projected<iterator_t<_Rp>, _Proj>> _Pred>
+ _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr borrowed_subrange_t<_Rp>
+  operator()(_Rp&& __r, _Pred __pred, _Proj __proj = {}) const {
+    auto __negate_pred = __make_negate_pred(__pred);
+    return ranges::__find_last_if_impl(ranges::begin(__r), ranges::end(__r), __negate_pred, __proj);
+  }
+};
+} // namespace __find_last_if_not
+
+inline namespace __cpo {
+inline constexpr auto find_last_if_not = __find_last_if_not::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 23
+
+#endif // _LIBCPP___ALGORITHM_RANGES_FIND_LAST_IF_NOT_H
diff --git a/libcxx/include/algorithm b/libcxx/include/algorithm
index 69ba9537dda6984..cfc4c09007c5b7f 100644
--- a/libcxx/include/algorithm
+++ b/libcxx/include/algorithm
@@ -99,6 +99,36 @@ namespace ranges {
     constexpr borrowed_iterator_t<R>
       find_if_not(R&& r, Pred pred, Proj proj = {});                                                      // since C++20
 
+  template<forward_iterator I, sentinel_for<I> S, class T, class Proj = identity>
+    requires indirect_binary_predicate<ranges::equal_to, projected<I, Proj>, const T*>
+    constexpr subrange<I>
+      ranges::find_last(I first, S last, const T& value, Proj proj = {});                                 // since C++23
+  
+  template<forward_range R, class T, class Proj = identity>
+    requires indirect_binary_predicate<ranges::equal_to, projected<iterator_t<R>, Proj>, const T*>
+    constexpr borrowed_subrange_t<R>
+      ranges::find_last(R&& r, const T& value, Proj proj = {});                                           //since C++23
+
+  template<forward_iterator I, sentinel_for<I> S, class Proj = identity,
+           indirect_unary_predicate<projected<I, Proj>> Pred>
+    constexpr subrange<I> 
+      ranges::find_last_if(I first, S last, Pred pred, Proj proj = {});                                   // since C++23
+
+  template<forward_range R, class Proj = identity,
+           indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
+    constexpr borrowed_subrange_t<R>
+      ranges::find_last_if(R&& r, Pred pred, Proj proj = {});                                             // since C++23
+
+  template<forward_iterator I, sentinel_for<I> S, class Proj = identity,
+           indirect_unary_predicate<projected<I, Proj>> Pred>
+    constexpr subrange<I> 
+      ranges::find_last_if_not(I first, S last, Pred pred, Proj proj = {});                               // since C++23
+
+  template<forward_range R, class Proj = identity,
+           indirect_unary_predicate<projected<iterator_t<R>, Proj>> Pred>
+    constexpr borrowed_subrange_t<R>
+      ranges::find_last_if_not(R&& r, Pred pred, Proj proj = {});                                         // since C++23
+
   template<class T, class Proj = identity,
            indirect_strict_weak_order<projected<const T*, Proj>> Comp = ranges::less>
     constexpr const T& min(const T& a, const T& b, Comp comp = {}, Proj proj = {});                       // since C++20
@@ -1859,6 +1889,9 @@ template <class BidirectionalIterator, class Compare>
 #include <__algorithm/ranges_find_first_of.h>
 #include <__algorithm/ranges_find_if.h>
 #include <__algorithm/ranges_find_if_not.h>
+#include <__algorithm/ranges_find_last.h>
+#include <__algorithm/ranges_find_last_if.h>
+#include <__algorithm/ranges_find_last_if_not.h>
 #include <__algorithm/ranges_for_each.h>
 #include <__algorithm/ranges_for_each_n.h>
 #include <__algorithm/ranges_generate.h>
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 6d9bb8653fcb5e9..eb42d6f59b138f4 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -842,6 +842,18 @@ module std_private_algorithm_ranges_find_end                             [system
 module std_private_algorithm_ranges_find_first_of                        [system] { header "__algorithm/ranges_find_first_of.h" }
 module std_private_algorithm_ranges_find_if                              [system] { header "__algorithm/ranges_find_if.h" }
 module std_private_algorithm_ranges_find_if_not                          [system] { header "__algorithm/ranges_find_if_not.h" }
+module std_private_algorithm_ranges_find_last                            [system] { 
+  header "__algorithm/ranges_find_last.h"
+  export std_private_ranges_subrange
+}
+module std_private_algorithm_ranges_find_last_if                         [system] { 
+  header "__algorithm/ranges_find_last_if.h"
+  export std_private_ranges_subrange
+}
+module std_private_algorithm_ranges_find_last_if_not                     [system] { 
+  header "__algorithm/ranges_find_last_if_not.h"
+  export std_private_ranges_subrange 
+}
 module std_private_algorithm_ranges_for_each                             [system] {
   header "__algorithm/ranges_for_each.h"
   export std_private_algorithm_in_fun_result
diff --git a/libcxx/include/version b/libcxx/include/version
index e5a995366a7aa48..2bacf36a6295cb7 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -90,6 +90,7 @@ __cpp_lib_execution                                     201902L <execution>
                                                         201603L // C++17
 __cpp_lib_expected                                      202211L <expected>
 __cpp_lib_filesystem                                    201703L <filesystem>
+__cpp_lib_find_last                                     202207L <algorithm>
 __cpp_lib_format                                        202106L <format>
 __cpp_lib_format_ranges                                 202207L <format>
 __cpp_lib_formatters                                    202302L <stacktrace> <thread>
@@ -422,6 +423,7 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 # define __cpp_lib_constexpr_memory                     202202L
 # define __cpp_lib_constexpr_typeinfo                   202106L
 # define __cpp_lib_expected                             202211L
+# define __cpp_lib_find_last                            202207L
 # define __cpp_lib_format_ranges                        202207L
 // # define __cpp_lib_formatters                           202302L
 # define __cpp_lib_forward_like                         202207L
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 eece9833f8a6357..85d5ba35d8184ba 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
@@ -118,6 +118,10 @@ constexpr bool all_the_algorithms()
     (void)std::ranges::find_if(a, UnaryTrue(&copies)); assert(copies == 0);
     (void)std::ranges::find_if_not(first, last, UnaryTrue(&copies)); assert(copies == 0);
     (void)std::ranges::find_if_not(a, UnaryTrue(&copies)); assert(copies == 0);
+    (void)std::ranges::find_last_if(first, last, UnaryTrue(&copies)); assert(copies == 1); copies = 0;
+    (void)std::ranges::find_last_if(a, UnaryTrue(&copies)); assert(copies == 1); copies = 0;
+    (void)std::ranges::find_last_if_not(first, last, UnaryTrue(&copies)); assert(copies == 0);
+    (void)std::ranges::find_last_if_not(a, UnaryTrue(&copies)); assert(copies == 0);
     (void)std::ranges::for_each(first, last, UnaryVoid(&copies)); assert(copies == 1); copies = 0;
     (void)std::ranges::for_each(a, UnaryVoid(&copies)); assert(copies == 1); copies = 0;
     (void)std::ranges::for_each_n(first, count, UnaryVoid(&copies)); assert(copies == 1); 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 afbbc224ea8644c..d3939881ab21ff6 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
@@ -104,6 +104,12 @@ constexpr bool all_the_algorithms()
     (void)std::ranges::find_if(a, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::find_if_not(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::find_if_not(a, UnaryTrue(), Proj(&copies)); assert(copies == 0);
+    (void)std::ranges::find_last(first, last, value, Proj(&copies)); assert(copies == 1); copies = 0;
+    (void)std::ranges::find_last(a, value, Proj(&copies)); assert(copies == 1); copies = 0;
+    (void)std::ranges::find_last_if(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 1); copies = 0;
+    (void)std::ranges::find_last_if(a, UnaryTrue(), Proj(&copies)); assert(copies == 1); copies = 0;
+    (void)std::ranges::find_last_if_not(first, last, UnaryTrue(), Proj(&copies)); assert(copies == 1); copies = 0;
+    (void)std::ranges::find_last_if_not(a, UnaryTrue(), Proj(&copies)); assert(copies == 1); copies = 0;
     (void)std::ranges::for_each(first, last, UnaryVoid(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::for_each(a, UnaryVoid(), Proj(&copies)); assert(copies == 0);
     (void)std::ranges::for_each_n(first, count, UnaryVoid(), Proj(&copies)); assert(copies == 0);
diff --git a/libcxx/test/libcxx/diagnostics/ranges.nodiscard_extensions.verify.cpp b/libcxx/test/libcxx/diagnostics/ranges.nodiscard_extensions.verify.cpp
index 77ac5f3f7790357..7528acdae28dfb3 100644
--- a/libcxx/test/libcxx/diagnostics/ranges.nodiscard_extensions.verify.cpp
+++ b/libcxx/test/libcxx/diagnostics/ranges.nodiscard_extensions.verify.cpp
@@ -41,6 +41,10 @@ void test() {
   std::ranges::find_if_not(iter, iter, pred); // expected-warning {{ignoring...
[truncated]

@github-actions
Copy link

github-actions bot commented Sep 24, 2023

✅ With the latest revision this PR passed the Python code formatter.

@github-actions
Copy link

github-actions bot commented Sep 24, 2023

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff 0d7c340c2cc89b71d398479c9adcac77f0336c6e d0fe8a81f52d766fbe62e6e3de3dd6f770d31c8e -- libcxx/include/__algorithm/ranges_find_last.h libcxx/include/__algorithm/ranges_find_last_if.h libcxx/include/__algorithm/ranges_find_last_if_not.h libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last.pass.cpp libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if.pass.cpp libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if_not.pass.cpp libcxx/include/algorithm libcxx/include/version 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/diagnostics/ranges.nodiscard_extensions.verify.cpp libcxx/test/std/language.support/support.limits/support.limits.general/algorithm.version.compile.pass.cpp libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp libcxx/test/support/almost_satisfies_types.h
View the diff from clang-format here.
diff --git a/libcxx/include/__algorithm/ranges_find_last.h b/libcxx/include/__algorithm/ranges_find_last.h
index 15c5380a983..c46f3ab360b 100644
--- a/libcxx/include/__algorithm/ranges_find_last.h
+++ b/libcxx/include/__algorithm/ranges_find_last.h
@@ -33,17 +33,17 @@ namespace ranges {
 namespace __find_last {
 struct __fn {
   template <forward_iterator _Ip, sentinel_for<_Ip> _Sp, class _Tp, class _Proj = identity>
-    requires indirect_binary_predicate < ranges::equal_to, projected<_Ip, _Proj>,
-  const _Tp* > _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr subrange<_Ip>
-               operator()(_Ip __first, _Sp __last, const _Tp& __value, _Proj __proj = {}) const {
+    requires indirect_binary_predicate< ranges::equal_to, projected<_Ip, _Proj>, const _Tp* >
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr subrange<_Ip>
+  operator()(_Ip __first, _Sp __last, const _Tp& __value, _Proj __proj = {}) const {
     auto __pred = [&](auto&& __e) { return std::forward<decltype(__e)>(__e) == __value; };
     return ranges::__find_last_if_impl(std::move(__first), std::move(__last), __pred, __proj);
   }
 
   template <forward_range _Rp, class _Tp, class _Proj = identity>
-    requires indirect_binary_predicate < ranges::equal_to, projected<iterator_t<_Rp>, _Proj>,
-  const _Tp* > _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr borrowed_subrange_t<_Rp>
-               operator()(_Rp&& __r, const _Tp& __value, _Proj __proj = {}) const {
+    requires indirect_binary_predicate< ranges::equal_to, projected<iterator_t<_Rp>, _Proj>, const _Tp* >
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr borrowed_subrange_t<_Rp>
+  operator()(_Rp&& __r, const _Tp& __value, _Proj __proj = {}) const {
     auto __pred = [&](auto&& __e) { return std::forward<decltype(__e)>(__e) == __value; };
     return ranges::__find_last_if_impl(ranges::begin(__r), ranges::end(__r), __pred, __proj);
   }
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last.pass.cpp
index 3ec1f7fed1a..21f87b51ad7 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last.pass.cpp
@@ -31,9 +31,7 @@
 struct NotEqualityComparable {};
 
 template <class It, class Sent = It>
-concept HasFindIt = requires(It it, Sent sent) {
-  std::ranges::find_last(it, sent, *it);
-};
+concept HasFindIt = requires(It it, Sent sent) { std::ranges::find_last(it, sent, *it); };
 static_assert(HasFindIt<int*>);
 static_assert(!HasFindIt<NotEqualityComparable*>);
 static_assert(!HasFindIt<InputIteratorNotDerivedFrom>);
@@ -46,9 +44,7 @@ static_assert(!HasFindIt<int*, int>);
 static_assert(!HasFindIt<int, int*>);
 
 template <class Range, class ValT>
-concept HasFindR = requires(Range r) {
-  std::ranges::find_last(r, ValT{});
-};
+concept HasFindR = requires(Range r) { std::ranges::find_last(r, ValT{}); };
 
 static_assert(HasFindR<std::array<int, 1>, int>);
 static_assert(!HasFindR<int, int>);
@@ -79,205 +75,213 @@ struct NonConstComparableRValue {
 };
 
 constexpr bool test() {
-  {// check that projections are used properly and called with the reference to the element the iterator is pointing to
-   {int a[] = {1, 2, 3, 4};
-  std::same_as<std::ranges::subrange<int*>> auto ret =
-      std::ranges::find_last(a, a + 4, a + 3, [](int& i) { return &i; });
-  assert(ret.data() == a + 3);
-}
-
-{
-  int a[]                                            = {1, 2, 3, 4};
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, a + 3, [](int& i) { return &i; });
-  assert(ret.data() == a + 3);
-}
-}
-
-{// check that the end element is returned
-
- {struct S{int comp;
-int other;
-}
-;
-
-S a[]                                                             = {{0, 0}, {0, 2}, {0, 1}};
-std::same_as<std::ranges::borrowed_subrange_t<S (&)[3]>> auto ret = std::ranges::find_last(a, 0, &S::comp);
-assert(ret.data() == a + 2);
-assert(ret.data()->comp == 0);
-assert(ret.data()->other == 1);
-}
-
-{
-  struct S {
-    int comp;
-    int other;
-  };
-
-  S a[]                                            = {{0, 0}, {0, 2}, {0, 1}};
-  std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last(a, a + 3, 0, &S::comp);
-  assert(ret.data() == a + 2);
-  assert(ret.data()->comp == 0);
-  assert(ret.data()->other == 1);
-}
-}
-
-{// check that end + 1 iterator is returned with no match
-
- {int a[] = {1, 1, 1};
-std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, a + 3, 0);
-assert(ret.data() == a + 3);
-}
-
-{
-  int a[]                                            = {1, 1, 1};
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, 0);
-  assert(ret.data() == a + 3);
-}
-}
-
-{ // check that ranges::dangling is returned
-  [[maybe_unused]] std::same_as<std::ranges::dangling> auto ret = std::ranges::find_last(std::array{1, 2}, 3);
-}
-
-{ // check that an iterator is returned with a borrowing range
-  int a[]                                            = {1, 1, 2, 3, 4};
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(std::views::all(a), 1);
-  assert(ret.data() == a + 1);
-  assert(*(ret.data()) == 1);
-}
-
-{// check that std::invoke is used
-
- {struct S{int i;
-}
-;
-
-S a[]                                                             = {S{1}, S{3}, S{2}};
-std::same_as<std::ranges::borrowed_subrange_t<S (&)[3]>> auto ret = std::ranges::find_last(a, 4, &S::i);
-assert(ret.data() == a + 3);
-}
-
-{
-  struct S {
-    int i;
-  };
+  { // check that projections are used properly and called with the reference to the element the iterator is pointing to
+    {
+      int a[] = {1, 2, 3, 4};
+      std::same_as<std::ranges::subrange<int*>> auto ret =
+          std::ranges::find_last(a, a + 4, a + 3, [](int& i) { return &i; });
+      assert(ret.data() == a + 3);
+    }
+
+    {
+      int a[]                                            = {1, 2, 3, 4};
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, a + 3, [](int& i) { return &i; });
+      assert(ret.data() == a + 3);
+    }
+  }
 
-  S a[]                                            = {S{1}, S{3}, S{2}};
-  std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last(a, a + 3, 4, &S::i);
-  assert(ret.data() == a + 3);
-}
-}
+  { // check that the end element is returned
+
+    {
+      struct S {
+        int comp;
+        int other;
+      };
+
+      S a[]                                                            = {{0, 0}, {0, 2}, {0, 1}};
+      std::same_as<std::ranges::borrowed_subrange_t<S(&)[3]>> auto ret = std::ranges::find_last(a, 0, &S::comp);
+      assert(ret.data() == a + 2);
+      assert(ret.data()->comp == 0);
+      assert(ret.data()->other == 1);
+    }
+
+    {
+      struct S {
+        int comp;
+        int other;
+      };
+
+      S a[]                                            = {{0, 0}, {0, 2}, {0, 1}};
+      std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last(a, a + 3, 0, &S::comp);
+      assert(ret.data() == a + 2);
+      assert(ret.data()->comp == 0);
+      assert(ret.data()->other == 1);
+    }
+  }
 
-{// count invocations of the projection
-
- {int a[] = {1, 2, 2, 3, 4};
-int projection_count                               = 0;
-std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, a + 5, 2, [&](int i) {
-  ++projection_count;
-  return i;
-});
-assert(ret.data() == a + 2);
-assert(*(ret.data()) == 2);
-assert(projection_count == 3);
-}
+  { // check that end + 1 iterator is returned with no match
 
-{
-  int a[]                                            = {1, 2, 2, 3, 4};
-  int projection_count                               = 0;
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, 2, [&](int i) {
-    ++projection_count;
-    return i;
-  });
-  assert(ret.data() == a + 2);
-  assert(*(ret.data()) == 2);
-  assert(projection_count == 3);
-}
-}
+    {
+      int a[]                                            = {1, 1, 1};
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, a + 3, 0);
+      assert(ret.data() == a + 3);
+    }
 
-{// check comparison order
+    {
+      int a[]                                            = {1, 1, 1};
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, 0);
+      assert(ret.data() == a + 3);
+    }
+  }
 
- {OneWayComparable a[] = {OneWayComparable{true}};
-std::same_as<std::ranges::subrange<OneWayComparable*>> auto ret =
-    std::ranges::find_last(a, a + 1, OneWayComparable{false});
-assert(ret.data() == a);
-}
+  { // check that ranges::dangling is returned
+    [[maybe_unused]] std::same_as<std::ranges::dangling> auto ret = std::ranges::find_last(std::array{1, 2}, 3);
+  }
 
-{
-  OneWayComparable a[] = {OneWayComparable{true}};
-  std::same_as<std::ranges::borrowed_subrange_t<OneWayComparable(&)[1]>> auto ret =
-      std::ranges::find_last(a, OneWayComparable{false});
-  assert(ret.data() == a);
-}
-}
+  { // check that an iterator is returned with a borrowing range
+    int a[]                                            = {1, 1, 2, 3, 4};
+    std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(std::views::all(a), 1);
+    assert(ret.data() == a + 1);
+    assert(*(ret.data()) == 1);
+  }
 
-{// check that the return type of `iter::operator*` doesn't change
+  { // check that std::invoke is used
 
- {NonConstComparableLValue a[] = {NonConstComparableLValue{}};
-std::same_as<std::ranges::subrange<NonConstComparableLValue*>> auto ret =
-    std::ranges::find_last(a, a + 1, NonConstComparableLValue{});
-assert(ret.data() == a);
-}
+    {
+      struct S {
+        int i;
+      };
 
-{
-  using It                     = std::move_iterator<NonConstComparableRValue*>;
-  NonConstComparableRValue a[] = {NonConstComparableRValue{}};
-  std::same_as<std::ranges::subrange<std::move_iterator<NonConstComparableRValue*>>> auto ret =
-      std::ranges::find_last(It(a), It(a + 1), NonConstComparableRValue{});
-  assert(ret.begin().base() == a);
-}
+      S a[]                                                            = {S{1}, S{3}, S{2}};
+      std::same_as<std::ranges::borrowed_subrange_t<S(&)[3]>> auto ret = std::ranges::find_last(a, 4, &S::i);
+      assert(ret.data() == a + 3);
+    }
 
-{
-  NonConstComparableLValue a[] = {NonConstComparableLValue{}};
-  std::same_as<std::ranges::borrowed_subrange_t<NonConstComparableLValue(&)[1]>> auto ret =
-      std::ranges::find_last(a, NonConstComparableLValue{});
-  assert(ret.data() == a);
-}
+    {
+      struct S {
+        int i;
+      };
 
-{
-  using It                     = std::move_iterator<NonConstComparableRValue*>;
-  NonConstComparableRValue a[] = {NonConstComparableRValue{}};
-  auto range                   = std::ranges::subrange(It(a), It(a + 1));
-  std::same_as<std::ranges::borrowed_subrange_t<std::ranges::subrange<std::move_iterator<NonConstComparableRValue*>,
-                                                                      std::move_iterator<NonConstComparableRValue*>,
-                                                                      std::ranges::subrange_kind::sized>&>> auto ret =
-      std::ranges::find_last(range, NonConstComparableRValue{});
-  assert(ret.begin().base() == a);
-}
-}
+      S a[]                                            = {S{1}, S{3}, S{2}};
+      std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last(a, a + 3, 4, &S::i);
+      assert(ret.data() == a + 3);
+    }
+  }
 
-{// check that an empty range works
- {std::array<int, 0> a = {};
-int search_value                                   = 1;
-std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a.begin(), a.end(), search_value);
-assert(ret.data() == a.end());
-}
+  { // count invocations of the projection
+
+    {
+      int a[]                                            = {1, 2, 2, 3, 4};
+      int projection_count                               = 0;
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, a + 5, 2, [&](int i) {
+        ++projection_count;
+        return i;
+      });
+      assert(ret.data() == a + 2);
+      assert(*(ret.data()) == 2);
+      assert(projection_count == 3);
+    }
+
+    {
+      int a[]                                            = {1, 2, 2, 3, 4};
+      int projection_count                               = 0;
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, 2, [&](int i) {
+        ++projection_count;
+        return i;
+      });
+      assert(ret.data() == a + 2);
+      assert(*(ret.data()) == 2);
+      assert(projection_count == 3);
+    }
+  }
 
-{
-  std::array<int, 0> a                               = {};
-  int search_value                                   = 1;
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, search_value);
-  assert(ret.data() == a.end());
-}
-}
+  { // check comparison order
+
+    {
+      OneWayComparable a[] = {OneWayComparable{true}};
+      std::same_as<std::ranges::subrange<OneWayComparable*>> auto ret =
+          std::ranges::find_last(a, a + 1, OneWayComparable{false});
+      assert(ret.data() == a);
+    }
+
+    {
+      OneWayComparable a[] = {OneWayComparable{true}};
+      std::same_as<std::ranges::borrowed_subrange_t<OneWayComparable(&)[1]>> auto ret =
+          std::ranges::find_last(a, OneWayComparable{false});
+      assert(ret.data() == a);
+    }
+  }
 
-{ // check that the implicit conversion to bool works
+  { // check that the return type of `iter::operator*` doesn't change
+
+    {
+      NonConstComparableLValue a[] = {NonConstComparableLValue{}};
+      std::same_as<std::ranges::subrange<NonConstComparableLValue*>> auto ret =
+          std::ranges::find_last(a, a + 1, NonConstComparableLValue{});
+      assert(ret.data() == a);
+    }
+
+    {
+      using It                     = std::move_iterator<NonConstComparableRValue*>;
+      NonConstComparableRValue a[] = {NonConstComparableRValue{}};
+      std::same_as<std::ranges::subrange<std::move_iterator<NonConstComparableRValue*>>> auto ret =
+          std::ranges::find_last(It(a), It(a + 1), NonConstComparableRValue{});
+      assert(ret.begin().base() == a);
+    }
+
+    {
+      NonConstComparableLValue a[] = {NonConstComparableLValue{}};
+      std::same_as<std::ranges::borrowed_subrange_t<NonConstComparableLValue(&)[1]>> auto ret =
+          std::ranges::find_last(a, NonConstComparableLValue{});
+      assert(ret.data() == a);
+    }
+
+    {
+      using It                     = std::move_iterator<NonConstComparableRValue*>;
+      NonConstComparableRValue a[] = {NonConstComparableRValue{}};
+      auto range                   = std::ranges::subrange(It(a), It(a + 1));
+      std::same_as<std::ranges::borrowed_subrange_t<std::ranges::subrange<std::move_iterator<NonConstComparableRValue*>,
+                                                                          std::move_iterator<NonConstComparableRValue*>,
+                                                                          std::ranges::subrange_kind::sized>&>> auto
+          ret = std::ranges::find_last(range, NonConstComparableRValue{});
+      assert(ret.begin().base() == a);
+    }
+  }
 
-  {
-    StrictComparable<int> a[] = {1, 2, 2, 3, 4};
-    std::same_as<std::ranges::subrange<StrictComparable<int>*>> auto ret =
-        std::ranges::find_last(a, a + 4, StrictComparable<int>{2});
-    assert(ret.data() == a + 2);
+  { // check that an empty range works
+    {
+      std::array<int, 0> a                               = {};
+      int search_value                                   = 1;
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a.begin(), a.end(), search_value);
+      assert(ret.data() == a.end());
+    }
+
+    {
+      std::array<int, 0> a                               = {};
+      int search_value                                   = 1;
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last(a, search_value);
+      assert(ret.data() == a.end());
+    }
   }
 
-  {
-    StrictComparable<int> a[] = {1, 2, 2, 3, 4};
-    std::same_as<std::ranges::borrowed_subrange_t<StrictComparable<int>(&)[5]>> auto ret =
-        std::ranges::find_last(a, StrictComparable<int>{2});
-    assert(ret.data() == a + 2);
+  { // check that the implicit conversion to bool works
+
+    {
+      StrictComparable<int> a[] = {1, 2, 2, 3, 4};
+      std::same_as<std::ranges::subrange<StrictComparable<int>*>> auto ret =
+          std::ranges::find_last(a, a + 4, StrictComparable<int>{2});
+      assert(ret.data() == a + 2);
+    }
+
+    {
+      StrictComparable<int> a[] = {1, 2, 2, 3, 4};
+      std::same_as<std::ranges::borrowed_subrange_t<StrictComparable<int>(&)[5]>> auto ret =
+          std::ranges::find_last(a, StrictComparable<int>{2});
+      assert(ret.data() == a + 2);
+    }
   }
-}
 
-return true;
+  return true;
 }
 
 int main(int, char**) {
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if.pass.cpp
index 881b71e7494..c145293f06c 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if.pass.cpp
@@ -32,9 +32,7 @@ struct Predicate {
 };
 
 template <class It, class Sent = It>
-concept HasFindLastIfIt = requires(It it, Sent sent) {
-  std::ranges::find_last_if(it, sent, Predicate{});
-};
+concept HasFindLastIfIt = requires(It it, Sent sent) { std::ranges::find_last_if(it, sent, Predicate{}); };
 static_assert(HasFindLastIfIt<int*>);
 static_assert(!HasFindLastIfIt<InputIteratorNotDerivedFrom>);
 static_assert(!HasFindLastIfIt<InputIteratorNotIndirectlyReadable>);
@@ -46,18 +44,14 @@ static_assert(!HasFindLastIfIt<int*, int>);
 static_assert(!HasFindLastIfIt<int, int*>);
 
 template <class Pred>
-concept HasFindLastIfPred = requires(int* it, Pred pred) {
-  std::ranges::find_last_if(it, it, pred);
-};
+concept HasFindLastIfPred = requires(int* it, Pred pred) { std::ranges::find_last_if(it, it, pred); };
 
 static_assert(HasFindLastIfPred<IndirectUnaryPredicate>);
 static_assert(!HasFindLastIfPred<IndirectUnaryPredicateNotCopyConstructible>);
 static_assert(!HasFindLastIfPred<IndirectUnaryPredicateNotPredicate>);
 
 template <class R>
-concept HasFindLastIfR = requires(R r) {
-  std::ranges::find_last_if(r, Predicate{});
-};
+concept HasFindLastIfR = requires(R r) { std::ranges::find_last_if(r, Predicate{}); };
 
 static_assert(HasFindLastIfR<std::array<int, 0>>);
 static_assert(!HasFindLastIfR<int>);
@@ -69,125 +63,132 @@ static_assert(!HasFindLastIfR<InputRangeNotSentinelEqualityComparableWith>);
 
 template <class It, class Sent = It>
 constexpr void test_iterators() {
-  {// Test with an empty range
-
-   {int a[] = {};
-  std::same_as<std::ranges::subrange<It>> auto ret =
-      std::ranges::find_last_if(It(a), Sent(It(a)), [](int x) { return x == 4; });
-  assert(ret.empty());
-}
-
-{
-  int a[]                                          = {};
-  auto range                                       = std::ranges::subrange(It(a), Sent(It(a)));
-  std::same_as<std::ranges::subrange<It>> auto ret = std::ranges::find_last_if(range, [](int x) { return x == 4; });
-  assert(ret.empty());
-}
-}
-
-{// Test with a single element range
-
- {int a[] = {4};
-std::same_as<std::ranges::subrange<It>> auto ret =
-    std::ranges::find_last_if(It(a), Sent(It(a + 1)), [](int x) { return x == 4; });
-assert(base(ret.begin()) == a);
-assert(*ret.begin() == 4);
-}
-
-{
-  int a[] = {4};
-  std::same_as<std::ranges::borrowed_subrange_t<int(&)[1]>> auto ret =
-      std::ranges::find_last_if(a, [](int x) { return x == 4; });
-  assert(base(ret.begin()) == a);
-  assert(*ret.begin() == 4);
-}
-}
-
-{// Test when no element satisfies the predicate
-
- {int a[] = {1, 2, 3, 4};
-std::same_as<std::ranges::subrange<It>> auto ret =
-    std::ranges::find_last_if(It(a), Sent(It(a + 4)), [](int x) { return x == 5; });
-assert(ret.empty());
-}
-
-{
-  int a[] = {1, 2, 3, 4};
-  std::same_as<std::ranges::borrowed_subrange_t<int(&)[4]>> auto ret =
-      std::ranges::find_last_if(a, [](int x) { return x == 5; });
-  assert(ret.empty());
-}
-}
-
-{// Test when all elements satisfy the predicate
-
- {int a[] = {4, 4, 4, 4};
-std::same_as<std::ranges::subrange<It>> auto ret =
-    std::ranges::find_last_if(It(a), Sent(It(a + 4)), [](int x) { return x == 4; });
-assert(base(ret.begin()) == a + 3);
-assert(*ret.begin() == 4);
-}
-
-{
-  int a[] = {4, 4, 4, 4};
-  std::same_as<std::ranges::borrowed_subrange_t<int(&)[4]>> auto ret =
-      std::ranges::find_last_if(a, [](int x) { return x == 4; });
-  assert(base(ret.begin()) == a + 3);
-  assert(*ret.begin() == 4);
-}
-}
-
-{// Test when the element being searched is the first one
+  { // Test with an empty range
+
+    {
+      int a[] = {};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if(It(a), Sent(It(a)), [](int x) { return x == 4; });
+      assert(ret.empty());
+    }
+
+    {
+      int a[]                                          = {};
+      auto range                                       = std::ranges::subrange(It(a), Sent(It(a)));
+      std::same_as<std::ranges::subrange<It>> auto ret = std::ranges::find_last_if(range, [](int x) { return x == 4; });
+      assert(ret.empty());
+    }
+  }
 
- {int a[] = {4, 1, 2, 3};
-std::same_as<std::ranges::subrange<It>> auto ret =
-    std::ranges::find_last_if(It(a), Sent(It(a + 4)), [](int x) { return x == 4; });
-assert(base(ret.begin()) == a);
-assert(*ret.begin() == 4);
-}
+  { // Test with a single element range
+
+    {
+      int a[] = {4};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if(It(a), Sent(It(a + 1)), [](int x) { return x == 4; });
+      assert(base(ret.begin()) == a);
+      assert(*ret.begin() == 4);
+    }
+
+    {
+      int a[] = {4};
+      std::same_as<std::ranges::borrowed_subrange_t<int(&)[1]>> auto ret =
+          std::ranges::find_last_if(a, [](int x) { return x == 4; });
+      assert(base(ret.begin()) == a);
+      assert(*ret.begin() == 4);
+    }
+  }
 
-{
-  int a[] = {4, 1, 2, 3};
-  std::same_as<std::ranges::borrowed_subrange_t<int(&)[4]>> auto ret =
-      std::ranges::find_last_if(a, [](int x) { return x == 4; });
-  assert(base(ret.begin()) == a);
-  assert(*ret.begin() == 4);
-}
-}
+  { // Test when no element satisfies the predicate
+
+    {
+      int a[] = {1, 2, 3, 4};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if(It(a), Sent(It(a + 4)), [](int x) { return x == 5; });
+      assert(ret.empty());
+    }
+
+    {
+      int a[] = {1, 2, 3, 4};
+      std::same_as<std::ranges::borrowed_subrange_t<int(&)[4]>> auto ret =
+          std::ranges::find_last_if(a, [](int x) { return x == 5; });
+      assert(ret.empty());
+    }
+  }
 
-{// Test when the element being searched is the last one
+  { // Test when all elements satisfy the predicate
+
+    {
+      int a[] = {4, 4, 4, 4};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if(It(a), Sent(It(a + 4)), [](int x) { return x == 4; });
+      assert(base(ret.begin()) == a + 3);
+      assert(*ret.begin() == 4);
+    }
+
+    {
+      int a[] = {4, 4, 4, 4};
+      std::same_as<std::ranges::borrowed_subrange_t<int(&)[4]>> auto ret =
+          std::ranges::find_last_if(a, [](int x) { return x == 4; });
+      assert(base(ret.begin()) == a + 3);
+      assert(*ret.begin() == 4);
+    }
+  }
 
- {int a[] = {1, 2, 3, 4};
-std::same_as<std::ranges::subrange<It>> auto ret =
-    std::ranges::find_last_if(It(a), Sent(It(a + 4)), [](int x) { return x == 4; });
-assert(base(ret.begin()) == a + 3);
-assert(*ret.begin() == 4);
-}
+  { // Test when the element being searched is the first one
+
+    {
+      int a[] = {4, 1, 2, 3};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if(It(a), Sent(It(a + 4)), [](int x) { return x == 4; });
+      assert(base(ret.begin()) == a);
+      assert(*ret.begin() == 4);
+    }
+
+    {
+      int a[] = {4, 1, 2, 3};
+      std::same_as<std::ranges::borrowed_subrange_t<int(&)[4]>> auto ret =
+          std::ranges::find_last_if(a, [](int x) { return x == 4; });
+      assert(base(ret.begin()) == a);
+      assert(*ret.begin() == 4);
+    }
+  }
 
-{
-  int a[] = {1, 2, 3, 4};
-  std::same_as<std::ranges::borrowed_subrange_t<int(&)[4]>> auto ret =
-      std::ranges::find_last_if(a, [](int x) { return x == 4; });
-  assert(base(ret.begin()) == a + 3);
-  assert(*ret.begin() == 4);
-}
-}
+  { // Test when the element being searched is the last one
+
+    {
+      int a[] = {1, 2, 3, 4};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if(It(a), Sent(It(a + 4)), [](int x) { return x == 4; });
+      assert(base(ret.begin()) == a + 3);
+      assert(*ret.begin() == 4);
+    }
+
+    {
+      int a[] = {1, 2, 3, 4};
+      std::same_as<std::ranges::borrowed_subrange_t<int(&)[4]>> auto ret =
+          std::ranges::find_last_if(a, [](int x) { return x == 4; });
+      assert(base(ret.begin()) == a + 3);
+      assert(*ret.begin() == 4);
+    }
+  }
 
-{ // check that past-the-end iterator is returned with no match
+  { // check that past-the-end iterator is returned with no match
 
-  {
-    int a[]                                            = {1, 1, 1};
-    std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(a, a + 3, [](int) { return false; });
-    assert(ret.data() == a + 3);
-  }
+    {
+      int a[] = {1, 1, 1};
+      std::same_as<std::ranges::subrange<int*>> auto ret =
+          std::ranges::find_last_if(a, a + 3, [](int) { return false; });
+      assert(ret.data() == a + 3);
+    }
 
-  {
-    int a[]                                            = {1, 1, 1};
-    std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(a, [](int) { return false; });
-    assert(ret.data() == a + 3);
+    {
+      int a[]                                            = {1, 1, 1};
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(a, [](int) { return false; });
+      assert(ret.data() == a + 3);
+    }
   }
 }
-}
 
 struct NonConstComparableValue {
   friend constexpr bool operator==(const NonConstComparableValue&, const NonConstComparableValue&) { return false; }
@@ -205,165 +206,170 @@ constexpr bool test() {
   test_iterators<random_access_iterator<int*>>();
   test_iterators<contiguous_iterator<int*>>();
 
-  {// check that projections are used properly and called with the reference to the element the iterator is pointing to
-
-   {int a[] = {1, 2, 3, 4};
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(
-      a, a + 4, [&](int* i) { return i == a + 3; }, [](int& i) { return &i; });
-  assert(ret.data() == a + 3);
-}
-
-{
-  int a[]                                            = {1, 2, 3, 4};
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(
-      a, [&](int* i) { return i == a + 3; }, [](int& i) { return &i; });
-  assert(ret.data() == a + 3);
-}
-}
-
-{// check that the last element is returned
-
- {struct S{int comp;
-int other;
-}
-;
-
-S a[]                                                             = {{0, 0}, {0, 2}, {0, 1}};
-std::same_as<std::ranges::borrowed_subrange_t<S (&)[3]>> auto ret = std::ranges::find_last_if(
-    a, [](int i) { return i == 0; }, &S::comp);
-assert(ret.data() == a + 2);
-assert(ret.data()->comp == 0);
-assert(ret.data()->other == 1);
-}
-
-{
-  struct S {
-    int comp;
-    int other;
-  };
-
-  S a[]                                            = {{0, 0}, {0, 2}, {0, 1}};
-  std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last_if(
-      a, a + 3, [](int i) { return i == 0; }, &S::comp);
-  assert(ret.data() == a + 2);
-  assert(ret.data()->comp == 0);
-  assert(ret.data()->other == 1);
-}
-}
-
-{ // check that ranges::dangling is returned
-  [[maybe_unused]] std::same_as<std::ranges::dangling> auto ret =
-      std::ranges::find_last_if(std::array{1, 2}, [](int) { return false; });
-}
-
-{ // check that an iterator is returned with a borrowing range
-  int a[] = {1, 1, 2, 3, 4};
-  std::same_as<std::ranges::subrange<int*>> auto ret =
-      std::ranges::find_last_if(std::views::all(a), [](int) { return true; });
-  assert(ret.data() == a + 4);
-}
-
-{// check that std::invoke is used
-
- {struct S{int i;
-}
-;
-
-S a[]                                                             = {S{1}, S{3}, S{2}};
-std::same_as<std::ranges::borrowed_subrange_t<S (&)[3]>> auto ret = std::ranges::find_last_if(
-    a, [](int) { return false; }, &S::i);
-assert(ret.data() == a + 3);
-}
-
-{
-  struct S {
-    int i;
-  };
-
-  S a[]                                            = {S{1}, S{3}, S{2}};
-  std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last_if(
-      a, a + 3, [](int) { return false; }, &S::i);
-  assert(ret.data() == a + 3);
-}
-}
-
-{// count projection and predicate invocation count
-
- {int a[] = {1, 2, 2, 3, 4};
-int predicate_count                                = 0;
-int projection_count                               = 0;
-std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(
-    a,
-    a + 5,
-    [&](int i) {
-      ++predicate_count;
-      return i == 2;
-    },
-    [&](int i) {
-      ++projection_count;
-      return i;
-    });
-assert(ret.data() == a + 2);
-assert(*(ret.data()) == 2);
-assert(predicate_count == 3);
-assert(projection_count == 3);
-}
+  { // check that projections are used properly and called with the reference to the element the iterator is pointing to
+
+    {
+      int a[]                                            = {1, 2, 3, 4};
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(
+          a, a + 4, [&](int* i) { return i == a + 3; }, [](int& i) { return &i; });
+      assert(ret.data() == a + 3);
+    }
+
+    {
+      int a[]                                            = {1, 2, 3, 4};
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(
+          a, [&](int* i) { return i == a + 3; }, [](int& i) { return &i; });
+      assert(ret.data() == a + 3);
+    }
+  }
 
-{
-  int a[]                                            = {1, 2, 2, 3, 4};
-  int predicate_count                                = 0;
-  int projection_count                               = 0;
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(
-      a,
-      [&](int i) {
-        ++predicate_count;
-        return i == 2;
-      },
-      [&](int i) {
-        ++projection_count;
-        return i;
-      });
-  assert(ret.data() == a + 2);
-  assert(*(ret.data()) == 2);
-  assert(predicate_count == 3);
-  assert(projection_count == 3);
-}
-}
+  { // check that the last element is returned
+
+    {
+      struct S {
+        int comp;
+        int other;
+      };
+
+      S a[]                                                            = {{0, 0}, {0, 2}, {0, 1}};
+      std::same_as<std::ranges::borrowed_subrange_t<S(&)[3]>> auto ret = std::ranges::find_last_if(
+          a, [](int i) { return i == 0; }, &S::comp);
+      assert(ret.data() == a + 2);
+      assert(ret.data()->comp == 0);
+      assert(ret.data()->other == 1);
+    }
+
+    {
+      struct S {
+        int comp;
+        int other;
+      };
+
+      S a[]                                            = {{0, 0}, {0, 2}, {0, 1}};
+      std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last_if(
+          a, a + 3, [](int i) { return i == 0; }, &S::comp);
+      assert(ret.data() == a + 2);
+      assert(ret.data()->comp == 0);
+      assert(ret.data()->other == 1);
+    }
+  }
 
-{// check that the return type of `iter::operator*` doesn't change
+  { // check that ranges::dangling is returned
+    [[maybe_unused]] std::same_as<std::ranges::dangling> auto ret =
+        std::ranges::find_last_if(std::array{1, 2}, [](int) { return false; });
+  }
 
- {NonConstComparableValue a[] = {NonConstComparableValue{}};
-std::same_as<std::ranges::subrange<NonConstComparableValue*>> auto ret =
-    std::ranges::find_last_if(a, a + 1, [](auto&& e) { return e == NonConstComparableValue{}; });
-assert(ret.data() == a);
-}
+  { // check that an iterator is returned with a borrowing range
+    int a[] = {1, 1, 2, 3, 4};
+    std::same_as<std::ranges::subrange<int*>> auto ret =
+        std::ranges::find_last_if(std::views::all(a), [](int) { return true; });
+    assert(ret.data() == a + 4);
+  }
 
-{
-  NonConstComparableValue a[] = {NonConstComparableValue{}};
-  std::same_as<std::ranges::borrowed_subrange_t<NonConstComparableValue(&)[1]>> auto ret =
-      std::ranges::find_last_if(a, [](auto&& e) { return e == NonConstComparableValue{}; });
-  assert(ret.data() == a);
-}
-}
+  { // check that std::invoke is used
+
+    {
+      struct S {
+        int i;
+      };
+
+      S a[]                                                            = {S{1}, S{3}, S{2}};
+      std::same_as<std::ranges::borrowed_subrange_t<S(&)[3]>> auto ret = std::ranges::find_last_if(
+          a, [](int) { return false; }, &S::i);
+      assert(ret.data() == a + 3);
+    }
+
+    {
+      struct S {
+        int i;
+      };
+
+      S a[]                                            = {S{1}, S{3}, S{2}};
+      std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last_if(
+          a, a + 3, [](int) { return false; }, &S::i);
+      assert(ret.data() == a + 3);
+    }
+  }
 
-{ // check that the implicit conversion to bool works
+  { // count projection and predicate invocation count
+
+    {
+      int a[]                                            = {1, 2, 2, 3, 4};
+      int predicate_count                                = 0;
+      int projection_count                               = 0;
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(
+          a,
+          a + 5,
+          [&](int i) {
+            ++predicate_count;
+            return i == 2;
+          },
+          [&](int i) {
+            ++projection_count;
+            return i;
+          });
+      assert(ret.data() == a + 2);
+      assert(*(ret.data()) == 2);
+      assert(predicate_count == 3);
+      assert(projection_count == 3);
+    }
+
+    {
+      int a[]                                            = {1, 2, 2, 3, 4};
+      int predicate_count                                = 0;
+      int projection_count                               = 0;
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if(
+          a,
+          [&](int i) {
+            ++predicate_count;
+            return i == 2;
+          },
+          [&](int i) {
+            ++projection_count;
+            return i;
+          });
+      assert(ret.data() == a + 2);
+      assert(*(ret.data()) == 2);
+      assert(predicate_count == 3);
+      assert(projection_count == 3);
+    }
+  }
 
-  {
-    int a[] = {1, 2, 3, 3, 4};
-    std::same_as<std::ranges::subrange<int*>> auto ret =
-        std::ranges::find_last_if(a, a + 4, [](const int& i) { return BooleanTestable{i == 3}; });
-    assert(ret.data() == a + 3);
+  { // check that the return type of `iter::operator*` doesn't change
+
+    {
+      NonConstComparableValue a[] = {NonConstComparableValue{}};
+      std::same_as<std::ranges::subrange<NonConstComparableValue*>> auto ret =
+          std::ranges::find_last_if(a, a + 1, [](auto&& e) { return e == NonConstComparableValue{}; });
+      assert(ret.data() == a);
+    }
+
+    {
+      NonConstComparableValue a[] = {NonConstComparableValue{}};
+      std::same_as<std::ranges::borrowed_subrange_t<NonConstComparableValue(&)[1]>> auto ret =
+          std::ranges::find_last_if(a, [](auto&& e) { return e == NonConstComparableValue{}; });
+      assert(ret.data() == a);
+    }
   }
 
-  {
-    int a[] = {1, 2, 3, 3, 4};
-    std::same_as<std::ranges::subrange<int*>> auto ret =
-        std::ranges::find_last_if(a, [](const int& b) { return BooleanTestable{b == 3}; });
-    assert(ret.data() == a + 3);
+  { // check that the implicit conversion to bool works
+
+    {
+      int a[] = {1, 2, 3, 3, 4};
+      std::same_as<std::ranges::subrange<int*>> auto ret =
+          std::ranges::find_last_if(a, a + 4, [](const int& i) { return BooleanTestable{i == 3}; });
+      assert(ret.data() == a + 3);
+    }
+
+    {
+      int a[] = {1, 2, 3, 3, 4};
+      std::same_as<std::ranges::subrange<int*>> auto ret =
+          std::ranges::find_last_if(a, [](const int& b) { return BooleanTestable{b == 3}; });
+      assert(ret.data() == a + 3);
+    }
   }
-}
 
-return true;
+  return true;
 }
 
 int main(int, char**) {
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if_not.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if_not.pass.cpp
index 76d3e7148a8..52a4266e9b9 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if_not.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find_last_if_not.pass.cpp
@@ -32,9 +32,7 @@ struct Predicate {
 };
 
 template <class It, class Sent = It>
-concept HasFindLastIfNotIt = requires(It it, Sent sent) {
-  std::ranges::find_last_if_not(it, sent, Predicate{});
-};
+concept HasFindLastIfNotIt = requires(It it, Sent sent) { std::ranges::find_last_if_not(it, sent, Predicate{}); };
 static_assert(HasFindLastIfNotIt<int*>);
 static_assert(!HasFindLastIfNotIt<InputIteratorNotDerivedFrom>);
 static_assert(!HasFindLastIfNotIt<InputIteratorNotIndirectlyReadable>);
@@ -46,18 +44,14 @@ static_assert(!HasFindLastIfNotIt<int*, int>);
 static_assert(!HasFindLastIfNotIt<int, int*>);
 
 template <class Pred>
-concept HasFindLastIfNotPred = requires(int* it, Pred pred) {
-  std::ranges::find_last_if_not(it, it, pred);
-};
+concept HasFindLastIfNotPred = requires(int* it, Pred pred) { std::ranges::find_last_if_not(it, it, pred); };
 
 static_assert(HasFindLastIfNotPred<IndirectUnaryPredicate>);
 static_assert(!HasFindLastIfNotPred<IndirectUnaryPredicateNotCopyConstructible>);
 static_assert(!HasFindLastIfNotPred<IndirectUnaryPredicateNotPredicate>);
 
 template <class R>
-concept HasFindLastIfNotR = requires(R r) {
-  std::ranges::find_last_if_not(r, Predicate{});
-};
+concept HasFindLastIfNotR = requires(R r) { std::ranges::find_last_if_not(r, Predicate{}); };
 static_assert(HasFindLastIfNotR<std::array<int, 0>>);
 static_assert(!HasFindLastIfNotR<int>);
 static_assert(!HasFindLastIfNotR<InputRangeNotDerivedFrom>);
@@ -68,77 +62,81 @@ static_assert(!HasFindLastIfNotR<InputRangeNotSentinelEqualityComparableWith>);
 
 template <class It, class Sent = It>
 constexpr void test_iterators() {
-  {// Test with an empty range
-
-   {int a[] = {};
-  std::same_as<std::ranges::subrange<It>> auto ret =
-      std::ranges::find_last_if_not(It(a), Sent(It(a)), [](int) { return false; });
-  assert(ret.empty());
-}
-
-{
-  int a[]                                          = {};
-  auto range                                       = std::ranges::subrange(It(a), Sent(It(a)));
-  std::same_as<std::ranges::subrange<It>> auto ret = std::ranges::find_last_if_not(range, [](int) { return false; });
-  assert(ret.empty());
-}
-}
-
-{// Test with a single element range
-
- {int a[] = {1};
-std::same_as<std::ranges::subrange<It>> auto ret =
-    std::ranges::find_last_if_not(It(a), Sent(It(a + 1)), [](int) { return false; });
-assert(base(ret.begin()) == a);
-assert(*ret.begin() == 1);
-}
-
-{
-  int a[] = {1};
-  std::same_as<std::ranges::borrowed_subrange_t<int(&)[1]>> auto ret =
-      std::ranges::find_last_if_not(a, [](int) { return false; });
-  assert(base(ret.begin()) == a);
-  assert(*ret.begin() == 1);
-}
-}
-
-{// Test with a range where each element satisfies the predicate
-
- {int a[] = {1, 2, 3, 4, 5};
-std::same_as<std::ranges::subrange<It>> auto ret =
-    std::ranges::find_last_if_not(It(a), Sent(It(a + 5)), [](int) { return false; });
-assert(base(ret.begin()) == a + 4);
-assert(*ret.begin() == 5);
-}
-
-{
-  int a[] = {1, 2, 3, 4, 5};
-  std::same_as<std::ranges::borrowed_subrange_t<int(&)[5]>> auto ret =
-      std::ranges::find_last_if_not(a, [](int) { return false; });
-  assert(base(ret.begin()) == a + 4);
-  assert(*ret.begin() == 5);
-}
-}
+  { // Test with an empty range
+
+    {
+      int a[] = {};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if_not(It(a), Sent(It(a)), [](int) { return false; });
+      assert(ret.empty());
+    }
+
+    {
+      int a[]    = {};
+      auto range = std::ranges::subrange(It(a), Sent(It(a)));
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if_not(range, [](int) { return false; });
+      assert(ret.empty());
+    }
+  }
 
-{ // Test with a range where no element satisfies the predicate
+  { // Test with a single element range
+
+    {
+      int a[] = {1};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if_not(It(a), Sent(It(a + 1)), [](int) { return false; });
+      assert(base(ret.begin()) == a);
+      assert(*ret.begin() == 1);
+    }
+
+    {
+      int a[] = {1};
+      std::same_as<std::ranges::borrowed_subrange_t<int(&)[1]>> auto ret =
+          std::ranges::find_last_if_not(a, [](int) { return false; });
+      assert(base(ret.begin()) == a);
+      assert(*ret.begin() == 1);
+    }
+  }
 
-  {
-    int a[] = {1, 2, 3, 4, 5};
-    std::same_as<std::ranges::subrange<It>> auto ret =
-        std::ranges::find_last_if_not(It(a), Sent(It(a + 5)), [](int) { return true; });
-    //assert(base(ret.begin()) == a + 5);
-    assert(ret.empty());
+  { // Test with a range where each element satisfies the predicate
+
+    {
+      int a[] = {1, 2, 3, 4, 5};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if_not(It(a), Sent(It(a + 5)), [](int) { return false; });
+      assert(base(ret.begin()) == a + 4);
+      assert(*ret.begin() == 5);
+    }
+
+    {
+      int a[] = {1, 2, 3, 4, 5};
+      std::same_as<std::ranges::borrowed_subrange_t<int(&)[5]>> auto ret =
+          std::ranges::find_last_if_not(a, [](int) { return false; });
+      assert(base(ret.begin()) == a + 4);
+      assert(*ret.begin() == 5);
+    }
   }
 
-  {
-    int a[] = {1, 2, 3, 4, 5};
-    std::same_as<std::ranges::borrowed_subrange_t<int(&)[5]>> auto ret =
-        std::ranges::find_last_if_not(a, [](int) { return true; });
-    //assert(base(ret.begin()) == a + 5);
-    assert(ret.empty());
+  { // Test with a range where no element satisfies the predicate
+
+    {
+      int a[] = {1, 2, 3, 4, 5};
+      std::same_as<std::ranges::subrange<It>> auto ret =
+          std::ranges::find_last_if_not(It(a), Sent(It(a + 5)), [](int) { return true; });
+      //assert(base(ret.begin()) == a + 5);
+      assert(ret.empty());
+    }
+
+    {
+      int a[] = {1, 2, 3, 4, 5};
+      std::same_as<std::ranges::borrowed_subrange_t<int(&)[5]>> auto ret =
+          std::ranges::find_last_if_not(a, [](int) { return true; });
+      //assert(base(ret.begin()) == a + 5);
+      assert(ret.empty());
+    }
   }
 }
-}
 
 struct NonConstComparableValue {
   friend constexpr bool operator==(const NonConstComparableValue&, const NonConstComparableValue&) { return false; }
@@ -156,178 +154,185 @@ constexpr bool test() {
   test_iterators<random_access_iterator<int*>>();
   test_iterators<contiguous_iterator<int*>>();
 
-  {// check that projections are used properly and called with the reference to the element the iterator is pointing to
-
-   {int a[] = {1, 2, 3, 4, 5};
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(
-      a, a + 5, [&](int* i) { return i != a + 3; }, [](int& i) { return &i; });
-  assert(ret.data() == a + 3);
-}
-
-{
-  int a[]                                            = {1, 2, 3, 4, 5};
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(
-      a, [&](int* i) { return i != a + 3; }, [](int& i) { return &i; });
-  assert(ret.data() == a + 3);
-}
-}
-
-{// check that the last element is returned
-
- {struct S{int comp;
-int other;
-}
-;
-
-S a[]                                                             = {{0, 0}, {0, 2}, {0, 1}};
-std::same_as<std::ranges::borrowed_subrange_t<S (&)[3]>> auto ret = std::ranges::find_last_if_not(
-    a, [](int i) { return i != 0; }, &S::comp);
-assert(ret.data() == a + 2);
-assert(ret.data()->comp == 0);
-assert(ret.data()->other == 1);
-}
-
-{
-  struct S {
-    int comp;
-    int other;
-  };
-
-  S a[]                                            = {{0, 0}, {0, 2}, {0, 1}};
-  std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last_if_not(
-      a, a + 3, [](int i) { return i != 0; }, &S::comp);
-  assert(ret.data() == a + 2);
-  assert(ret.data()->comp == 0);
-  assert(ret.data()->other == 1);
-}
-}
-
-{// check that end iterator is returned with no match
-
- {int a[] = {1, 1, 1};
-std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(a, a + 3, [](int) { return false; });
-assert(ret.data() == a + 2);
-}
-
-{
-  int a[]                                            = {1, 1, 1};
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(a, [](int) { return false; });
-  assert(ret.data() == a + 2);
-}
-}
-
-{ // check that ranges::dangling is returned
-  [[maybe_unused]] std::same_as<std::ranges::dangling> auto ret =
-      std::ranges::find_last_if_not(std::array{1, 2}, [](int) { return true; });
-}
-
-{ // check that an iterator is returned with a borrowing range
-  int a[] = {1, 2, 3, 4};
-  std::same_as<std::ranges::subrange<int*>> auto ret =
-      std::ranges::find_last_if_not(std::views::all(a), [](int) { return false; });
-  assert(ret.data() == a + 3);
-  assert(*ret.data() == 4);
-}
-
-{// check that std::invoke is used
-
- {struct S{int i;
-}
-;
+  { // check that projections are used properly and called with the reference to the element the iterator is pointing to
+
+    {
+      int a[]                                            = {1, 2, 3, 4, 5};
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(
+          a, a + 5, [&](int* i) { return i != a + 3; }, [](int& i) { return &i; });
+      assert(ret.data() == a + 3);
+    }
+
+    {
+      int a[]                                            = {1, 2, 3, 4, 5};
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(
+          a, [&](int* i) { return i != a + 3; }, [](int& i) { return &i; });
+      assert(ret.data() == a + 3);
+    }
+  }
 
-S a[]                                                             = {S{1}, S{3}, S{2}};
-std::same_as<std::ranges::borrowed_subrange_t<S (&)[3]>> auto ret = std::ranges::find_last_if_not(
-    a, [](int) { return true; }, &S::i);
-assert(ret.data() == a + 3);
-}
+  { // check that the last element is returned
+
+    {
+      struct S {
+        int comp;
+        int other;
+      };
+
+      S a[]                                                            = {{0, 0}, {0, 2}, {0, 1}};
+      std::same_as<std::ranges::borrowed_subrange_t<S(&)[3]>> auto ret = std::ranges::find_last_if_not(
+          a, [](int i) { return i != 0; }, &S::comp);
+      assert(ret.data() == a + 2);
+      assert(ret.data()->comp == 0);
+      assert(ret.data()->other == 1);
+    }
+
+    {
+      struct S {
+        int comp;
+        int other;
+      };
+
+      S a[]                                            = {{0, 0}, {0, 2}, {0, 1}};
+      std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last_if_not(
+          a, a + 3, [](int i) { return i != 0; }, &S::comp);
+      assert(ret.data() == a + 2);
+      assert(ret.data()->comp == 0);
+      assert(ret.data()->other == 1);
+    }
+  }
 
-{
-  struct S {
-    int i;
-  };
+  { // check that end iterator is returned with no match
 
-  S a[]                                            = {S{1}, S{3}, S{2}};
-  std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last_if_not(
-      a, a + 3, [](int) { return true; }, &S::i);
-  assert(ret.data() == a + 3);
-}
-}
+    {
+      int a[] = {1, 1, 1};
+      std::same_as<std::ranges::subrange<int*>> auto ret =
+          std::ranges::find_last_if_not(a, a + 3, [](int) { return false; });
+      assert(ret.data() == a + 2);
+    }
 
-{// count projection and predicate invocation count
-
- {int a[] = {1, 2, 3, 4};
-int predicate_count                                = 0;
-int projection_count                               = 0;
-std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(
-    a,
-    a + 4,
-    [&](int i) {
-      ++predicate_count;
-      return i != 2;
-    },
-    [&](int i) {
-      ++projection_count;
-      return i;
-    });
-assert(ret.data() == a + 1);
-assert(*ret.data() == 2);
-assert(predicate_count == 3);
-assert(projection_count == 3);
-}
+    {
+      int a[]                                            = {1, 1, 1};
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(a, [](int) { return false; });
+      assert(ret.data() == a + 2);
+    }
+  }
 
-{
-  int a[]                                            = {1, 2, 3, 4};
-  int predicate_count                                = 0;
-  int projection_count                               = 0;
-  std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(
-      a,
-      [&](int i) {
-        ++predicate_count;
-        return i != 2;
-      },
-      [&](int i) {
-        ++projection_count;
-        return i;
-      });
-  assert(ret.data() == a + 1);
-  assert(*ret.data() == 2);
-  assert(predicate_count == 3);
-  assert(projection_count == 3);
-}
-}
+  { // check that ranges::dangling is returned
+    [[maybe_unused]] std::same_as<std::ranges::dangling> auto ret =
+        std::ranges::find_last_if_not(std::array{1, 2}, [](int) { return true; });
+  }
 
-{// check that the return type of `iter::operator*` doesn't change
- {NonConstComparableValue a[] = {NonConstComparableValue{}};
-std::same_as<std::ranges::subrange<NonConstComparableValue*>> auto ret =
-    std::ranges::find_last_if_not(a, a + 1, [](auto&& e) { return e != NonConstComparableValue{}; });
-assert(ret.data() == a);
-}
+  { // check that an iterator is returned with a borrowing range
+    int a[] = {1, 2, 3, 4};
+    std::same_as<std::ranges::subrange<int*>> auto ret =
+        std::ranges::find_last_if_not(std::views::all(a), [](int) { return false; });
+    assert(ret.data() == a + 3);
+    assert(*ret.data() == 4);
+  }
 
-{
-  NonConstComparableValue a[] = {NonConstComparableValue{}};
-  std::same_as<std::ranges::borrowed_subrange_t<NonConstComparableValue(&)[1]>> auto ret =
-      std::ranges::find_last_if_not(a, [](auto&& e) { return e != NonConstComparableValue{}; });
-  assert(ret.data() == a);
-}
-}
+  { // check that std::invoke is used
+
+    {
+      struct S {
+        int i;
+      };
+
+      S a[]                                                            = {S{1}, S{3}, S{2}};
+      std::same_as<std::ranges::borrowed_subrange_t<S(&)[3]>> auto ret = std::ranges::find_last_if_not(
+          a, [](int) { return true; }, &S::i);
+      assert(ret.data() == a + 3);
+    }
+
+    {
+      struct S {
+        int i;
+      };
+
+      S a[]                                            = {S{1}, S{3}, S{2}};
+      std::same_as<std::ranges::subrange<S*>> auto ret = std::ranges::find_last_if_not(
+          a, a + 3, [](int) { return true; }, &S::i);
+      assert(ret.data() == a + 3);
+    }
+  }
 
-{ // check that the implicit conversion to bool works
+  { // count projection and predicate invocation count
+
+    {
+      int a[]                                            = {1, 2, 3, 4};
+      int predicate_count                                = 0;
+      int projection_count                               = 0;
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(
+          a,
+          a + 4,
+          [&](int i) {
+            ++predicate_count;
+            return i != 2;
+          },
+          [&](int i) {
+            ++projection_count;
+            return i;
+          });
+      assert(ret.data() == a + 1);
+      assert(*ret.data() == 2);
+      assert(predicate_count == 3);
+      assert(projection_count == 3);
+    }
+
+    {
+      int a[]                                            = {1, 2, 3, 4};
+      int predicate_count                                = 0;
+      int projection_count                               = 0;
+      std::same_as<std::ranges::subrange<int*>> auto ret = std::ranges::find_last_if_not(
+          a,
+          [&](int i) {
+            ++predicate_count;
+            return i != 2;
+          },
+          [&](int i) {
+            ++projection_count;
+            return i;
+          });
+      assert(ret.data() == a + 1);
+      assert(*ret.data() == 2);
+      assert(predicate_count == 3);
+      assert(projection_count == 3);
+    }
+  }
 
-  {
-    int a[] = {1, 2, 3, 4};
-    std::same_as<std::ranges::subrange<int*>> auto ret =
-        std::ranges::find_last_if_not(a, a + 4, [](const int& i) { return BooleanTestable{i != 3}; });
-    assert(ret.data() == a + 2);
+  { // check that the return type of `iter::operator*` doesn't change
+    {
+      NonConstComparableValue a[] = {NonConstComparableValue{}};
+      std::same_as<std::ranges::subrange<NonConstComparableValue*>> auto ret =
+          std::ranges::find_last_if_not(a, a + 1, [](auto&& e) { return e != NonConstComparableValue{}; });
+      assert(ret.data() == a);
+    }
+
+    {
+      NonConstComparableValue a[] = {NonConstComparableValue{}};
+      std::same_as<std::ranges::borrowed_subrange_t<NonConstComparableValue(&)[1]>> auto ret =
+          std::ranges::find_last_if_not(a, [](auto&& e) { return e != NonConstComparableValue{}; });
+      assert(ret.data() == a);
+    }
   }
-  {
-    int a[] = {1, 2, 3, 4};
-    std::same_as<std::ranges::subrange<int*>> auto ret =
-        std::ranges::find_last_if_not(a, [](const int& b) { return BooleanTestable{b != 3}; });
-    assert(ret.data() == a + 2);
+
+  { // check that the implicit conversion to bool works
+
+    {
+      int a[] = {1, 2, 3, 4};
+      std::same_as<std::ranges::subrange<int*>> auto ret =
+          std::ranges::find_last_if_not(a, a + 4, [](const int& i) { return BooleanTestable{i != 3}; });
+      assert(ret.data() == a + 2);
+    }
+    {
+      int a[] = {1, 2, 3, 4};
+      std::same_as<std::ranges::subrange<int*>> auto ret =
+          std::ranges::find_last_if_not(a, [](const int& b) { return BooleanTestable{b != 3}; });
+      assert(ret.data() == a + 2);
+    }
   }
-}
 
-return true;
+  return true;
 }
 
 int main(int, char**) {

@phyBrackets
Copy link
Member Author

I will fix the formatting issues tomorrow :)

@phyBrackets
Copy link
Member Author

I'm not sure about the format issues, it looks weird formatting at the same time for the tests.

@ldionne
Copy link
Member

ldionne commented Sep 25, 2023

@phyBrackets Don't format all the tests. There's new infrastructure to check formatting and it behaves differently from our previous one, so it reports the tests as being mis-formatted. We'll fix that, but for now just ignore these failures. The buildkite/github-pull-requests failures are the ones you want to check for now.

@ldionne
Copy link
Member

ldionne commented Sep 25, 2023

Note that if you rebase onto main now, the Github Actions shouldn't complain spuriously anymore. It should only complain about stuff that you should fix for real.

}
++__first;
}
return {__result, __last};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct. __last is of type _Sp. You actually want to return the __first here because it is the correct type _Ip and it reaches the __last at this point.
Do you have a test for this? If _Sp is different type than _Ip, this would fail.


template <class _Ip, class _Sp, class _Pred, class _Proj>
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI static constexpr subrange<_Ip>
__find_last_if_impl(_Ip __first, _Sp __last, _Pred __pred, _Proj __proj) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this is an algorithm that is worth do some optimisations depending on the types of the inputs. If the input models bidirectional_range && common_range, you would search from the end, and you leave this implementation as a fallback if the inputs do not model the concept

Copy link
Contributor

@cjdb cjdb Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also bidirectional_iterator<I> and assignable_from<I&, S&>

Comment on lines 205 to 210
test_iterators<const int*>();
test_iterators<int*>();
test_iterators<bidirectional_iterator<int*>>();
test_iterators<forward_iterator<int*>>();
test_iterators<random_access_iterator<int*>>();
test_iterators<contiguous_iterator<int*>>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please have tests when sentinel is a different type than the iterator.
This applies for other tests

@philnik777 philnik777 added the ranges Issues related to `<ranges>` label Oct 6, 2023
@var-const
Copy link
Member

@phyBrackets Do you plan to continue working on this? Please let us know if you'd like someone else to pick up this patch (I can volunteer).

@phyBrackets
Copy link
Member Author

Hi @var-const , yes I'll update it asap. Just get busy with my university exams and few other things. Apologies for the delay.

@hawkinsw
Copy link
Contributor

Hello @phyBrackets and @var-const -- I am happy to help, too, if you'd like!!

template <class _Ip, class _Sp, class _Pred, class _Proj>
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI static constexpr subrange<_Ip>
__find_last_if_impl(_Ip __first, _Sp __last, _Pred __pred, _Proj __proj) {
_Ip __result = __last;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires assignable_from<_Ip&, _Sp&>, so we need to use ranges::next.

Suggested change
_Ip __result = __last;
_Ip __result = ranges::next(__first, __last);

sentinel_for<_Ip> _Sp,
class _Proj = identity,
indirect_unary_predicate<projected<_Ip, _Proj>> _Pred>
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr subrange<_Ip>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add tests for the [[nodiscard]] extensions.

Copy link
Contributor

@cjdb cjdb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer it if we implemented find_last and find_last_if independently, instead of deferring to an implementation function.


template <class _Ip, class _Sp, class _Pred, class _Proj>
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI static constexpr subrange<_Ip>
__find_last_if_impl(_Ip __first, _Sp __last, _Pred __pred, _Proj __proj) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
__find_last_if_impl(_Ip __first, _Sp __last, _Pred __pred, _Proj __proj) {
__find_last_if_impl(_Ip __first, _Sp __last, _Pred&& __pred, _Proj&& __proj) {

We don't want to copy __pred and __proj, and references are nicer than std::reference_wrapper.

@philnik777
Copy link
Contributor

I'd prefer it if we implemented find_last and find_last_if independently, instead of deferring to an implementation function.

Why?

@cjdb
Copy link
Contributor

cjdb commented Oct 16, 2023

I'd prefer it if we implemented find_last and find_last_if independently, instead of deferring to an implementation function.

Why?

It simplifies the implementation so that we can directly call x == y, rather than having to go through std::invoke, which is currently opaque and bad for debug info.

@philnik777
Copy link
Contributor

I'd prefer it if we implemented find_last and find_last_if independently, instead of deferring to an implementation function.

Why?

It simplifies the implementation so that we can directly call x == y, rather than having to go through std::invoke, which is currently opaque and bad for debug info.

It also means we'll have to duplicate optimizations, which significantly increases the complexity of the implementation. Improving the code gen of std::invoke seems like a really good idea regardless, so IMO that should be the way to go instead. I'd be happy to work on that with you (you already started working on that, right?).

libcxx/include/__algorithm/ranges_find_last.h Outdated Show resolved Hide resolved
libcxx/include/__algorithm/ranges_find_last.h Outdated Show resolved Hide resolved
libcxx/include/__algorithm/ranges_find_last_if.h Outdated Show resolved Hide resolved
libcxx/include/__algorithm/ranges_find_last_if.h Outdated Show resolved Hide resolved
@huixie90
Copy link
Contributor

huixie90 commented Nov 2, 2023

I'd prefer it if we implemented find_last and find_last_if independently, instead of deferring to an implementation function.

Why?

It simplifies the implementation so that we can directly call x == y, rather than having to go through std::invoke, which is currently opaque and bad for debug info.

I don’t think it simplifies the “implementation” of Libc++, but quite opposite, make it very complex. You can see the every optimisation overload is duplicated in this review. Even my code review comments to these optimisations are duplicated. I think you are effectively suggesting all library writers : duplicate code if you can because it release the burden of compiler optimisations and debugging tool. This just adds burden to library writers.

@philnik777
Copy link
Contributor

I very much agree with @huixie90. Please revert to forwarding find_last to find_last_if. We really shouldn't duplicate this to make -O0 a tiny bit faster.

@var-const
Copy link
Member

Agree with @huixie90 and @philnik777 -- code duplication is a big issue in our code base, we should try to keep it minimal.

@cjdb
Copy link
Contributor

cjdb commented Nov 15, 2023

I'd prefer it if we implemented find_last and find_last_if independently, instead of deferring to an implementation function.

Why?

It simplifies the implementation so that we can directly call x == y, rather than having to go through std::invoke, which is currently opaque and bad for debug info.

It also means we'll have to duplicate optimizations, which significantly increases the complexity of the implementation. Improving the code gen of std::invoke seems like a really good idea regardless, so IMO that should be the way to go instead. I'd be happy to work on that with you (you already started working on that, right?).

I thought I replied to this, but evidently not. Yes, I've got a patch for Clang that transforms std::invoke directly into its underlying logic. The patch in question needs to have runtime tests added, but is otherwise robust (as far as I know). This, plus transforming the standard function objects into their underlying operations (e.g. std::equal_to()(x, y) becoming x == y) would permanently eliminate these concerns.

requires indirect_binary_predicate<ranges::equal_to, projected<iterator_t<_Rp>, _Proj>, const _Tp*>
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr borrowed_subrange_t<_Rp>
operator()(_Rp&& __r, const _Tp& __value, _Proj __proj = {}) const {
return operator()(ranges::begin(__r), ranges::end(__r), __value, __proj);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return operator()(ranges::begin(__r), ranges::end(__r), __value, __proj);
[[clang::always_inline]] return operator()(ranges::begin(__r), ranges::end(__r), __value, std::ref(__proj));

If we're to keep this as a function call, then we should inline it, so as to not penalise the caller. We also need to not copy __proj.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we use always_inline anywhere in the library. If clang failed to inline this function call, I would file a bug report to clang

@phyBrackets
Copy link
Member Author

Hi @huixie90 apologies for taking long time, Can you please review the changes? I will make it correctly format and rebase.

Comment on lines +49 to +51
} else if constexpr (bidirectional_iterator<_Ip> && assignable_from<_Ip&, _Sp&>) {
// For non-common but bidirectional ranges
_Ip __result = ranges::next(__first, __last);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make sure ranges::next is constant time, I think you can do

(random_access_iterator<_Ip> && sized_sentinel_for<_Sp, _Ip>) || (bidirectional_iterator<_Ip> && assignable_from<_Ip&, _Sp&>)

template <class _Ip, class _Sp, class _Pred, class _Proj>
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI static constexpr subrange<_Ip>
__find_last_if_impl(_Ip __first, _Sp __last, _Pred&& __pred, _Proj&& __proj) {
if constexpr (common_range<_Ip> && bidirectional_range<_Ip>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect. since here you passed in iterator and sentinel, not the range itself, so they will never be common_range or bidirectional_range. I think your second branch almost covers it except that instead of directly copying the last, it copies the first then assigns it to the last. If we want to keep this branch, I think the correct check is to check iterator and sentinel are the same type and the iterator models bidi iterator

}
++__first;
}
return {__result, __original_first};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value is not correct. you want to return the incremented iterator which equals to last

Returns: Let i be the last iterator in the range [first, last) for which E is true. Returns {i, last}, or {last, last} if no such iterator is found.

Please add tests after the fix

@ldionne
Copy link
Member

ldionne commented Jul 18, 2024

I am going to close this as inactive, since it hasn't been updated in a very long time and I want to avoid losing momentum on #99312.

@ldionne ldionne closed this Jul 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. ranges Issues related to `<ranges>`
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants