[libcxx-commits] [libcxx] [libc++] Refactor std::equal to forward to the 3-leg overload if the size of the ranges is known (PR #171585)

via libcxx-commits libcxx-commits at lists.llvm.org
Thu Dec 18 09:16:47 PST 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

<details>
<summary>Changes</summary>

This allows us to merge some optimizations common between the 3-leg overload and the two ranges overload.

In some cases this could also improve performance, since we avoid checking one of the iterators if the size if the ranges is known. I haven't been able to show any improvements though.

This is also a prerequisite for optimizing `std::search`.


---
Full diff: https://github.com/llvm/llvm-project/pull/171585.diff


4 Files Affected:

- (modified) libcxx/include/__algorithm/equal.h (+42-54) 
- (modified) libcxx/include/__algorithm/ranges_equal.h (+18-27) 
- (modified) libcxx/include/__bit_reference (+13-3) 
- (modified) libcxx/test/std/utilities/memory/specialized.algorithms/buffer.h (+1-1) 


``````````diff
diff --git a/libcxx/include/__algorithm/equal.h b/libcxx/include/__algorithm/equal.h
index 5a8c9504ede1d..63df043263cbe 100644
--- a/libcxx/include/__algorithm/equal.h
+++ b/libcxx/include/__algorithm/equal.h
@@ -160,22 +160,28 @@ template <class _Cp,
           bool _IsConst1,
           bool _IsConst2,
           class _BinaryPredicate,
-          __enable_if_t<__desugars_to_v<__equal_tag, _BinaryPredicate, bool, bool>, int> = 0>
+          class _Proj1,
+          class _Proj2,
+          __enable_if_t<__is_identity<_Proj1>::value && __is_identity<_Proj2>::value &&
+                            __desugars_to_v<__equal_tag, _BinaryPredicate, bool, bool>,
+                        int> = 0>
 [[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __equal_iter_impl(
     __bit_iterator<_Cp, _IsConst1> __first1,
     __bit_iterator<_Cp, _IsConst1> __last1,
     __bit_iterator<_Cp, _IsConst2> __first2,
-    _BinaryPredicate) {
+    _BinaryPredicate,
+    _Proj1&,
+    _Proj2&) {
   if (__first1.__ctz_ == __first2.__ctz_)
     return std::__equal_aligned(__first1, __last1, __first2);
   return std::__equal_unaligned(__first1, __last1, __first2);
 }
 
-template <class _InputIterator1, class _InputIterator2, class _BinaryPredicate>
+template <class _InIter1, class _Sent1, class _InIter2, class _Pred, class _Proj1, class _Proj2>
 [[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __equal_iter_impl(
-    _InputIterator1 __first1, _InputIterator1 __last1, _InputIterator2 __first2, _BinaryPredicate& __pred) {
+    _InIter1 __first1, _Sent1 __last1, _InIter2 __first2, _Pred& __pred, _Proj1& __proj1, _Proj2& __proj2) {
   for (; __first1 != __last1; ++__first1, (void)++__first2)
-    if (!__pred(*__first1, *__first2))
+    if (!std::__invoke(__pred, std::__invoke(__proj1, *__first1), std::__invoke(__proj2, *__first2)))
       return false;
   return true;
 }
@@ -183,19 +189,23 @@ template <class _InputIterator1, class _InputIterator2, class _BinaryPredicate>
 template <class _Tp,
           class _Up,
           class _BinaryPredicate,
-          __enable_if_t<__desugars_to_v<__equal_tag, _BinaryPredicate, _Tp, _Up> && !is_volatile<_Tp>::value &&
+          class _Proj1,
+          class _Proj2,
+          __enable_if_t<__is_identity<_Proj1>::value && __is_identity<_Proj2>::value &&
+                            __desugars_to_v<__equal_tag, _BinaryPredicate, _Tp, _Up> && !is_volatile<_Tp>::value &&
                             !is_volatile<_Up>::value && __libcpp_is_trivially_equality_comparable<_Tp, _Up>::value,
                         int> = 0>
 [[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool
-__equal_iter_impl(_Tp* __first1, _Tp* __last1, _Up* __first2, _BinaryPredicate&) {
+__equal_iter_impl(_Tp* __first1, _Tp* __last1, _Up* __first2, _BinaryPredicate&, _Proj1&, _Proj2&) {
   return std::__constexpr_memcmp_equal(__first1, __first2, __element_count(__last1 - __first1));
 }
 
 template <class _InputIterator1, class _InputIterator2, class _BinaryPredicate>
 [[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool
 equal(_InputIterator1 __first1, _InputIterator1 __last1, _InputIterator2 __first2, _BinaryPredicate __pred) {
+  __identity __proj;
   return std::__equal_iter_impl(
-      std::__unwrap_iter(__first1), std::__unwrap_iter(__last1), std::__unwrap_iter(__first2), __pred);
+      std::__unwrap_iter(__first1), std::__unwrap_iter(__last1), std::__unwrap_iter(__first2), __pred, __proj, __proj);
 }
 
 template <class _InputIterator1, class _InputIterator2>
@@ -206,52 +216,28 @@ equal(_InputIterator1 __first1, _InputIterator1 __last1, _InputIterator2 __first
 
 #if _LIBCPP_STD_VER >= 14
 
-template <class _Iter1, class _Sent1, class _Iter2, class _Sent2, class _Pred, class _Proj1, class _Proj2>
-[[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __equal_impl(
-    _Iter1 __first1, _Sent1 __last1, _Iter2 __first2, _Sent2 __last2, _Pred& __comp, _Proj1& __proj1, _Proj2& __proj2) {
-  while (__first1 != __last1 && __first2 != __last2) {
-    if (!std::__invoke(__comp, std::__invoke(__proj1, *__first1), std::__invoke(__proj2, *__first2)))
-      return false;
-    ++__first1;
-    ++__first2;
-  }
-  return __first1 == __last1 && __first2 == __last2;
-}
-
-template <class _Tp,
-          class _Up,
-          class _Pred,
-          class _Proj1,
-          class _Proj2,
-          __enable_if_t<__desugars_to_v<__equal_tag, _Pred, _Tp, _Up> && __is_identity<_Proj1>::value &&
-                            __is_identity<_Proj2>::value && !is_volatile<_Tp>::value && !is_volatile<_Up>::value &&
-                            __libcpp_is_trivially_equality_comparable<_Tp, _Up>::value,
-                        int> = 0>
-[[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool
-__equal_impl(_Tp* __first1, _Tp* __last1, _Up* __first2, _Up*, _Pred&, _Proj1&, _Proj2&) {
-  return std::__constexpr_memcmp_equal(__first1, __first2, __element_count(__last1 - __first1));
-}
-
-template <class _Cp,
-          bool _IsConst1,
-          bool _IsConst2,
+template <bool __known_equal_length,
+          class _Iter1,
+          class _Sent1,
+          class _Iter2,
+          class _Sent2,
           class _Pred,
           class _Proj1,
-          class _Proj2,
-          __enable_if_t<__desugars_to_v<__equal_tag, _Pred, bool, bool> && __is_identity<_Proj1>::value &&
-                            __is_identity<_Proj2>::value,
-                        int> = 0>
+          class _Proj2>
 [[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __equal_impl(
-    __bit_iterator<_Cp, _IsConst1> __first1,
-    __bit_iterator<_Cp, _IsConst1> __last1,
-    __bit_iterator<_Cp, _IsConst2> __first2,
-    __bit_iterator<_Cp, _IsConst2>,
-    _Pred&,
-    _Proj1&,
-    _Proj2&) {
-  if (__first1.__ctz_ == __first2.__ctz_)
-    return std::__equal_aligned(__first1, __last1, __first2);
-  return std::__equal_unaligned(__first1, __last1, __first2);
+    _Iter1 __first1, _Sent1 __last1, _Iter2 __first2, _Sent2 __last2, _Pred& __comp, _Proj1& __proj1, _Proj2& __proj2) {
+  if constexpr (__known_equal_length) {
+    return std::__equal_iter_impl(
+        std::move(__first1), std::move(__last1), std::move(__first2), __comp, __proj1, __proj2);
+  } else {
+    while (__first1 != __last1 && __first2 != __last2) {
+      if (!std::__invoke(__comp, std::__invoke(__proj1, *__first1), std::__invoke(__proj2, *__first2)))
+        return false;
+      ++__first1;
+      ++__first2;
+    }
+    return __first1 == __last1 && __first2 == __last2;
+  }
 }
 
 template <class _InputIterator1, class _InputIterator2, class _BinaryPredicate>
@@ -261,13 +247,15 @@ equal(_InputIterator1 __first1,
       _InputIterator2 __first2,
       _InputIterator2 __last2,
       _BinaryPredicate __pred) {
-  if constexpr (__has_random_access_iterator_category<_InputIterator1>::value &&
-                __has_random_access_iterator_category<_InputIterator2>::value) {
+  static constexpr bool __both_random_access =
+      __has_random_access_iterator_category<_InputIterator1>::value &&
+      __has_random_access_iterator_category<_InputIterator2>::value;
+  if constexpr (__both_random_access) {
     if (std::distance(__first1, __last1) != std::distance(__first2, __last2))
       return false;
   }
   __identity __proj;
-  return std::__equal_impl(
+  return std::__equal_impl<__both_random_access>(
       std::__unwrap_iter(__first1),
       std::__unwrap_iter(__last1),
       std::__unwrap_iter(__first2),
diff --git a/libcxx/include/__algorithm/ranges_equal.h b/libcxx/include/__algorithm/ranges_equal.h
index c26d13f002201..300ff13faf711 100644
--- a/libcxx/include/__algorithm/ranges_equal.h
+++ b/libcxx/include/__algorithm/ranges_equal.h
@@ -13,13 +13,12 @@
 #include <__algorithm/unwrap_range.h>
 #include <__config>
 #include <__functional/identity.h>
-#include <__functional/invoke.h>
 #include <__functional/ranges_operations.h>
 #include <__iterator/concepts.h>
-#include <__iterator/distance.h>
 #include <__iterator/indirectly_comparable.h>
 #include <__ranges/access.h>
 #include <__ranges/concepts.h>
+#include <__ranges/size.h>
 #include <__utility/move.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -35,6 +34,8 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 namespace ranges {
 struct __equal {
+
+
   template <input_iterator _Iter1,
             sentinel_for<_Iter1> _Sent1,
             input_iterator _Iter2,
@@ -51,20 +52,15 @@ struct __equal {
       _Pred __pred   = {},
       _Proj1 __proj1 = {},
       _Proj2 __proj2 = {}) const {
-    if constexpr (sized_sentinel_for<_Sent1, _Iter1> && sized_sentinel_for<_Sent2, _Iter2>) {
-      if (__last1 - __first1 != __last2 - __first2)
+    auto [__ufirst1, __ulast1] = std::__unwrap_range(std::move(__first1), std::move(__last1));
+    auto [__ufirst2, __ulast2] = std::__unwrap_range(std::move(__first2), std::move(__last2));
+    static constexpr bool __both_sized = sized_sentinel_for<_Sent1, _Iter1> && sized_sentinel_for<_Sent2, _Iter2>;
+    if constexpr (__both_sized) {
+      if (__ulast1 - __ufirst1 != __ulast2 - __ufirst2)
         return false;
     }
-    auto __unwrapped1 = std::__unwrap_range(std::move(__first1), std::move(__last1));
-    auto __unwrapped2 = std::__unwrap_range(std::move(__first2), std::move(__last2));
-    return std::__equal_impl(
-        std::move(__unwrapped1.first),
-        std::move(__unwrapped1.second),
-        std::move(__unwrapped2.first),
-        std::move(__unwrapped2.second),
-        __pred,
-        __proj1,
-        __proj2);
+    return std::__equal_impl<__both_sized>(
+        std::move(__ufirst1), std::move(__ulast1), std::move(__ufirst2), std::move(__ulast2), __pred, __proj1, __proj2);
   }
 
   template <input_range _Range1,
@@ -75,21 +71,16 @@ struct __equal {
     requires indirectly_comparable<iterator_t<_Range1>, iterator_t<_Range2>, _Pred, _Proj1, _Proj2>
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(
       _Range1&& __range1, _Range2&& __range2, _Pred __pred = {}, _Proj1 __proj1 = {}, _Proj2 __proj2 = {}) const {
-    if constexpr (sized_range<_Range1> && sized_range<_Range2>) {
-      if (ranges::distance(__range1) != ranges::distance(__range2))
+    static constexpr bool __both_sized = sized_range<_Range1> && sized_range<_Range2>;
+    if constexpr (__both_sized) {
+      if (ranges::size(__range1) != ranges::size(__range2))
         return false;
     }
-    auto __unwrapped1 = std::__unwrap_range(ranges::begin(__range1), ranges::end(__range1));
-    auto __unwrapped2 = std::__unwrap_range(ranges::begin(__range2), ranges::end(__range2));
-    return std::__equal_impl(
-        std::move(__unwrapped1.first),
-        std::move(__unwrapped1.second),
-        std::move(__unwrapped2.first),
-        std::move(__unwrapped2.second),
-        __pred,
-        __proj1,
-        __proj2);
-    return false;
+
+    auto [__ufirst1, __ulast1] = std::__unwrap_range(ranges::begin(__range1), ranges::end(__range1));
+    auto [__ufirst2, __ulast2] = std::__unwrap_range(ranges::begin(__range2), ranges::end(__range2));
+    return std::__equal_impl<__both_sized>(
+        std::move(__ufirst1), std::move(__ulast1), std::move(__ufirst2), std::move(__ulast2), __pred, __proj1, __proj2);
   }
 };
 
diff --git a/libcxx/include/__bit_reference b/libcxx/include/__bit_reference
index 00bbc4100ddb9..082a0575ee7dc 100644
--- a/libcxx/include/__bit_reference
+++ b/libcxx/include/__bit_reference
@@ -509,10 +509,20 @@ private:
             bool _IsConst1,
             bool _IsConst2,
             class _BinaryPredicate,
-            __enable_if_t<__desugars_to_v<__equal_tag, _BinaryPredicate, bool, bool>, int> >
+            class _Proj1,
+            class _Proj2,
+            __enable_if_t<__is_identity<_Proj1>::value && __is_identity<_Proj2>::value &&
+                              __desugars_to_v<__equal_tag, _BinaryPredicate, bool, bool>,
+                          int> >
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 friend bool __equal_iter_impl(
-      __bit_iterator<_Dp, _IsConst1>, __bit_iterator<_Dp, _IsConst1>, __bit_iterator<_Dp, _IsConst2>, _BinaryPredicate);
-  template <class _Dp,
+      __bit_iterator<_Dp, _IsConst1>,
+      __bit_iterator<_Dp, _IsConst1>,
+      __bit_iterator<_Dp, _IsConst2>,
+      _BinaryPredicate,
+      _Proj1&,
+      _Proj2&);
+  template <bool,
+            class _Dp,
             bool _IsConst1,
             bool _IsConst2,
             class _Pred,
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/buffer.h b/libcxx/test/std/utilities/memory/specialized.algorithms/buffer.h
index 1114fdc68e724..072c0546cd7be 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/buffer.h
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/buffer.h
@@ -19,7 +19,7 @@ struct Buffer {
   const T* cbegin() const { return reinterpret_cast<const T*>(buffer); }
   const T* cend() const { return cbegin() + N; }
 
-  constexpr int size() const { return N; }
+  constexpr unsigned size() const { return N; }
 };
 
 #endif // LIBCPP_TEST_STD_UTILITIES_MEMORY_SPECIALIZED_ALGORITHMS_BUFFER_H

``````````

</details>


https://github.com/llvm/llvm-project/pull/171585


More information about the libcxx-commits mailing list