[libcxx-commits] [libcxx] [libc++] Add segmented iterator optimization to std::equal (PR #179242)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Thu Feb 12 02:29:47 PST 2026


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/179242

>From b5c12842fffe3de766ccec4d731be52a5d108606 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Sun, 1 Feb 2026 18:01:41 +0100
Subject: [PATCH] [libc++] Add segmented iterator optimization to std::equal

---
 libcxx/include/__algorithm/equal.h            | 65 ++++++++++++--
 libcxx/include/__algorithm/find.h             |  9 +-
 libcxx/include/__algorithm/find_segment_if.h  | 17 ++--
 .../alg.equal/equal.segmented.pass.cpp        | 84 +++++++++++++++++++
 4 files changed, 151 insertions(+), 24 deletions(-)
 create mode 100644 libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/equal.segmented.pass.cpp

diff --git a/libcxx/include/__algorithm/equal.h b/libcxx/include/__algorithm/equal.h
index ca536dc61e235..d7ad342704096 100644
--- a/libcxx/include/__algorithm/equal.h
+++ b/libcxx/include/__algorithm/equal.h
@@ -11,17 +11,21 @@
 #define _LIBCPP___ALGORITHM_EQUAL_H
 
 #include <__algorithm/comp.h>
+#include <__algorithm/find_segment_if.h>
 #include <__algorithm/min.h>
 #include <__algorithm/unwrap_iter.h>
 #include <__config>
 #include <__functional/identity.h>
 #include <__fwd/bit_reference.h>
 #include <__iterator/iterator_traits.h>
+#include <__iterator/segmented_iterator.h>
 #include <__string/constexpr_c_functions.h>
+#include <__type_traits/common_type.h>
 #include <__type_traits/desugars_to.h>
 #include <__type_traits/enable_if.h>
 #include <__type_traits/invoke.h>
 #include <__type_traits/is_equality_comparable.h>
+#include <__type_traits/is_same.h>
 #include <__type_traits/is_volatile.h>
 #include <__utility/move.h>
 
@@ -174,15 +178,6 @@ template <class _Cp,
   return std::__equal_unaligned(__first1, __last1, __first2);
 }
 
-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(
-    _InIter1 __first1, _Sent1 __last1, _InIter2 __first2, _Pred& __pred, _Proj1& __proj1, _Proj2& __proj2) {
-  for (; __first1 != __last1; ++__first1, (void)++__first2)
-    if (!std::__invoke(__pred, std::__invoke(__proj1, *__first1), std::__invoke(__proj2, *__first2)))
-      return false;
-  return true;
-}
-
 template <class _Tp,
           class _Up,
           class _BinaryPredicate,
@@ -197,6 +192,58 @@ __equal_iter_impl(_Tp* __first1, _Tp* __last1, _Up* __first2, _BinaryPredicate&,
   return std::__constexpr_memcmp_equal(__first1, __first2, __element_count(__last1 - __first1));
 }
 
+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(
+    _InIter1 __first1, _Sent1 __last1, _InIter2 __first2, _Pred& __pred, _Proj1& __proj1, _Proj2& __proj2) {
+#ifndef _LIBCPP_CXX03_LANG
+  if constexpr (__has_random_access_iterator_category<_InIter1>::value &&
+                __has_random_access_iterator_category<_InIter2>::value) {
+    if constexpr (is_same<_InIter1, _Sent1>::value && __is_segmented_iterator_v<_InIter1>) {
+      using __local_iterator_t = typename __segmented_iterator_traits<_InIter1>::__local_iterator;
+      bool __is_equal          = true;
+      std::__find_segment_if(__first1, __last1, [&](__local_iterator_t __lfirst, __local_iterator_t __llast) {
+        if (std::__equal_iter_impl(
+                std::__unwrap_iter(__lfirst), std::__unwrap_iter(__llast), __first2, __pred, __proj1, __proj2)) {
+          __first2 += __llast - __lfirst;
+          return __llast;
+        }
+        __is_equal = false;
+        return __lfirst;
+      });
+      return __is_equal;
+    } else if constexpr (__is_segmented_iterator_v<_InIter2>) {
+      using _Traits = __segmented_iterator_traits<_InIter2>;
+      using _DiffT =
+          typename common_type<__iterator_difference_type<_InIter1>, __iterator_difference_type<_InIter2> >::type;
+
+      if (__first1 == __last1)
+        return true;
+
+      auto __local_first      = _Traits::__local(__first2);
+      auto __segment_iterator = _Traits::__segment(__first2);
+
+      while (true) {
+        auto __local_last = _Traits::__end(__segment_iterator);
+        auto __size       = std::min<_DiffT>(__local_last - __local_first, __last1 - __first1);
+        if (!std::__equal_iter_impl(
+                __first1, __first1 + __size, std::__unwrap_iter(__local_first), __pred, __proj1, __proj2))
+          return false;
+
+        __first1 += __size;
+        if (__first1 == __last1)
+          return true;
+
+        __local_first = _Traits::__begin(++__segment_iterator);
+      }
+    }
+  }
+#endif
+  for (; __first1 != __last1; ++__first1, (void)++__first2)
+    if (!std::__invoke(__pred, std::__invoke(__proj1, *__first1), std::__invoke(__proj2, *__first2)))
+      return false;
+  return true;
+}
+
 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) {
diff --git a/libcxx/include/__algorithm/find.h b/libcxx/include/__algorithm/find.h
index 852bc2da3eb7b..4e03fd7f70a1a 100644
--- a/libcxx/include/__algorithm/find.h
+++ b/libcxx/include/__algorithm/find.h
@@ -216,13 +216,10 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _SegmentedIterator
 __find(_SegmentedIterator __first, _SegmentedIterator __last, const _Tp& __value, _Proj& __proj) {
   using __local_iterator = typename __segmented_iterator_traits<_SegmentedIterator>::__local_iterator;
   return std::__find_segment_if(
-      std::move(__first),
-      std::move(__last),
-      [&__value](__local_iterator __lfirst, __local_iterator __llast, _Proj& __lproj) {
+      std::move(__first), std::move(__last), [&__value, &__proj](__local_iterator __lfirst, __local_iterator __llast) {
         return std::__rewrap_iter(
-            __lfirst, std::__find(std::__unwrap_iter(__lfirst), std::__unwrap_iter(__llast), __value, __lproj));
-      },
-      __proj);
+            __lfirst, std::__find(std::__unwrap_iter(__lfirst), std::__unwrap_iter(__llast), __value, __proj));
+      });
 }
 
 // public API
diff --git a/libcxx/include/__algorithm/find_segment_if.h b/libcxx/include/__algorithm/find_segment_if.h
index 9d6064f3e283a..558f8a108f3a9 100644
--- a/libcxx/include/__algorithm/find_segment_if.h
+++ b/libcxx/include/__algorithm/find_segment_if.h
@@ -19,14 +19,13 @@
 _LIBCPP_BEGIN_NAMESPACE_STD
 
 // __find_segment_if is a utility function for optimizing iteration over segmented iterators linearly.
-// [__first, __last) has to be a segmented range. __pred is expected to take a range of local iterators and the __proj.
+// [__first, __last) has to be a segmented range. __pred is expected to take a range of local iterators.
 // It returns an iterator to the first element that satisfies the predicate, or a one-past-the-end iterator if there was
-// no match. __proj may be anything that should be passed to __pred, but is expected to be a projection to support
-// ranges algorithms, or __identity for classic algorithms.
+// no match.
 
-template <class _SegmentedIterator, class _Pred, class _Proj>
+template <class _SegmentedIterator, class _Pred>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _SegmentedIterator
-__find_segment_if(_SegmentedIterator __first, _SegmentedIterator __last, _Pred __pred, _Proj& __proj) {
+__find_segment_if(_SegmentedIterator __first, _SegmentedIterator __last, _Pred __pred) {
   using _Traits = __segmented_iterator_traits<_SegmentedIterator>;
 
   auto __sfirst = _Traits::__segment(__first);
@@ -34,11 +33,11 @@ __find_segment_if(_SegmentedIterator __first, _SegmentedIterator __last, _Pred _
 
   // We are in a single segment, so we might not be at the beginning or end
   if (__sfirst == __slast)
-    return _Traits::__compose(__sfirst, __pred(_Traits::__local(__first), _Traits::__local(__last), __proj));
+    return _Traits::__compose(__sfirst, __pred(_Traits::__local(__first), _Traits::__local(__last)));
 
   { // We have more than one segment. Iterate over the first segment, since we might not start at the beginning
     auto __llast = _Traits::__end(__sfirst);
-    auto __liter = __pred(_Traits::__local(__first), __llast, __proj);
+    auto __liter = __pred(_Traits::__local(__first), __llast);
     if (__liter != __llast)
       return _Traits::__compose(__sfirst, __liter);
   }
@@ -47,14 +46,14 @@ __find_segment_if(_SegmentedIterator __first, _SegmentedIterator __last, _Pred _
   // Iterate over the segments which are guaranteed to be completely in the range
   while (__sfirst != __slast) {
     auto __llast = _Traits::__end(__sfirst);
-    auto __liter = __pred(_Traits::__begin(__sfirst), _Traits::__end(__sfirst), __proj);
+    auto __liter = __pred(_Traits::__begin(__sfirst), _Traits::__end(__sfirst));
     if (__liter != __llast)
       return _Traits::__compose(__sfirst, __liter);
     ++__sfirst;
   }
 
   // Iterate over the last segment
-  return _Traits::__compose(__sfirst, __pred(_Traits::__begin(__sfirst), _Traits::__local(__last), __proj));
+  return _Traits::__compose(__sfirst, __pred(_Traits::__begin(__sfirst), _Traits::__local(__last)));
 }
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/equal.segmented.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/equal.segmented.pass.cpp
new file mode 100644
index 0000000000000..f3783433861ad
--- /dev/null
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/equal.segmented.pass.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// std::equal(segmented-iterator, segmented-iterator, other-iterator);
+// std::equal(other-iterator, other-iterator, segmented-iterator);
+// std::equal(segmented-iterator, segmented-iterator, segmented-iterator);
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <algorithm>
+#include <cassert>
+#include <concepts>
+#include <deque>
+#include <vector>
+
+template <class InContainer1, class InContainer2>
+constexpr void test_containers() {
+  using InIter1 = typename InContainer1::iterator;
+  using InIter2 = typename InContainer2::iterator;
+
+  { // Simple positive test
+    InContainer1 lhs{1, 2, 3, 4};
+    InContainer2 rhs{1, 2, 3, 4};
+
+    std::same_as<bool> auto ret = std::equal(InIter1(lhs.begin()), InIter1(lhs.end()), InIter2(rhs.begin()));
+    assert(ret);
+  }
+  { // Simple negative test
+    InContainer1 lhs{1, 2, 3, 4};
+    InContainer2 rhs{2, 2, 3, 4};
+
+    std::same_as<bool> auto ret = std::equal(InIter1(lhs.begin()), InIter1(lhs.end()), InIter2(rhs.begin()));
+    assert(!ret);
+  }
+  { // Multiple segmentes are iterated, equal
+    InContainer1 lhs;
+    std::generate_n(std::back_inserter(lhs), 4095, [i = 0] mutable { return i++; });
+    InContainer2 rhs(lhs.begin(), lhs.end());
+
+    assert(std::equal(InIter1(lhs.begin()), InIter1(lhs.end()), InIter2(rhs.begin())));
+  }
+  { // Multiple segmentes are iterated, not equal
+    InContainer1 lhs;
+    std::generate_n(std::back_inserter(lhs), 4095, [i = 0] mutable { return i++; });
+    InContainer2 rhs(lhs.begin(), lhs.end());
+    rhs.back() = -1;
+
+    assert(!std::equal(InIter1(lhs.begin()), InIter1(lhs.end()), InIter2(rhs.begin())));
+  }
+  { // Multiple segmentes are iterated, first segment is partially iterated, equal
+    InContainer1 lhs;
+    std::generate_n(std::back_inserter(lhs), 4095, [i = 0] mutable { return i++; });
+    InContainer2 rhs;
+    rhs.resize(10);
+    rhs.insert(rhs.end(), lhs.begin(), lhs.end());
+    rhs.erase(rhs.begin(), rhs.begin() + 10);
+
+    assert(std::equal(InIter1(lhs.begin()), InIter1(lhs.end()), InIter2(rhs.begin())));
+  }
+  { // Multiple segmentes are iterated, first segment is partially iterated, not equal
+    InContainer1 lhs;
+    std::generate_n(std::back_inserter(lhs), 4095, [i = 0] mutable { return i++; });
+    InContainer2 rhs;
+    rhs.resize(10);
+    rhs.insert(rhs.end(), lhs.begin(), lhs.end());
+    rhs.erase(rhs.begin(), rhs.begin() + 10);
+    rhs.back() = -1;
+
+    assert(!std::equal(InIter1(lhs.begin()), InIter1(lhs.end()), InIter2(rhs.begin())));
+  }
+}
+
+int main(int, char**) {
+  test_containers<std::vector<int>, std::deque<int>>();
+  test_containers<std::deque<int>, std::vector<int>>();
+  test_containers<std::deque<int>, std::deque<int>>();
+
+  return 0;
+}



More information about the libcxx-commits mailing list