[libcxx-commits] [libcxx] [libc++][ranges] Fix `ranges::join_view` segmented iterator trait (PR #158347)

via libcxx-commits libcxx-commits at lists.llvm.org
Wed Sep 24 05:54:46 PDT 2025


https://github.com/lbonn updated https://github.com/llvm/llvm-project/pull/158347

>From b41db68fe58024e7a55cda56496d8c4fef1f4eb9 Mon Sep 17 00:00:00 2001
From: Laurent Bonnans <github at lbonnans.net>
Date: Fri, 19 Sep 2025 09:23:13 +0200
Subject: [PATCH] [libc++][ranges] Fix `ranges::join_view` segmented iterator
 trait

The outer iterator needs to move to the next segment when calling
__compose.

Without this change, `find_segment_if` would never reach the end of the
join_view which caused erroneous result when calling `ranges::find` on a
join_view of bidirectional ranges.
---
 libcxx/include/__ranges/join_view.h           |   9 +-
 .../alg.find/ranges.find.pass.cpp             | 121 ++++++++++++------
 2 files changed, 89 insertions(+), 41 deletions(-)

diff --git a/libcxx/include/__ranges/join_view.h b/libcxx/include/__ranges/join_view.h
index 327b349f476a7..364f056d8d2cf 100644
--- a/libcxx/include/__ranges/join_view.h
+++ b/libcxx/include/__ranges/join_view.h
@@ -410,8 +410,13 @@ struct __segmented_iterator_traits<_JoinViewIterator> {
 
   static constexpr _LIBCPP_HIDE_FROM_ABI _JoinViewIterator
   __compose(__segment_iterator __seg_iter, __local_iterator __local_iter) {
-    return _JoinViewIterator(
-        std::move(__seg_iter).__get_data(), std::move(__seg_iter).__get_iter(), std::move(__local_iter));
+    auto&& __parent = std::move(__seg_iter).__get_data();
+    auto&& __outer  = std::move(__seg_iter).__get_iter();
+    if (__local_iter == ranges::end(*__outer)) {
+      ++__outer;
+      return _JoinViewIterator(*__parent, __outer);
+    }
+    return _JoinViewIterator(__parent, __outer, std::move(__local_iter));
   }
 };
 
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find.pass.cpp
index 5b4abc45b6f4f..3303b4a76f467 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find.pass.cpp
@@ -272,57 +272,100 @@ class Comparable {
   friend bool operator==(const Comparable& lhs, long long rhs) { return comparable_data[lhs.index_] == rhs; }
 };
 
-void test_deque() {
-  { // empty deque
-    std::deque<int> data;
-    assert(std::ranges::find(data, 4) == data.end());
-    assert(std::ranges::find(data.begin(), data.end(), 4) == data.end());
-  }
-
-  { // single element - match
-    std::deque<int> data = {4};
-    assert(std::ranges::find(data, 4) == data.begin());
-    assert(std::ranges::find(data.begin(), data.end(), 4) == data.begin());
-  }
-
-  { // single element - no match
-    std::deque<int> data = {3};
-    assert(std::ranges::find(data, 4) == data.end());
-    assert(std::ranges::find(data.begin(), data.end(), 4) == data.end());
-  }
-
-  // many elements
-  for (auto size : {2, 3, 1023, 1024, 1025, 2047, 2048, 2049}) {
-    { // last element match
+void test_segmented_iterator_types() {
+  // Test the optimized find algorithm for types that implement the segment iterator trait
+  // deque
+  {
+    { // empty deque
       std::deque<int> data;
-      data.resize(size);
-      std::fill(data.begin(), data.end(), 3);
-      data[size - 1] = 4;
-      assert(std::ranges::find(data, 4) == data.end() - 1);
-      assert(std::ranges::find(data.begin(), data.end(), 4) == data.end() - 1);
+      assert(std::ranges::find(data, 4) == data.end());
+      assert(std::ranges::find(data.begin(), data.end(), 4) == data.end());
     }
 
-    { // second-last element match
-      std::deque<int> data;
-      data.resize(size);
-      std::fill(data.begin(), data.end(), 3);
-      data[size - 2] = 4;
-      assert(std::ranges::find(data, 4) == data.end() - 2);
-      assert(std::ranges::find(data.begin(), data.end(), 4) == data.end() - 2);
+    { // single element - match
+      std::deque<int> data = {4};
+      assert(std::ranges::find(data, 4) == data.begin());
+      assert(std::ranges::find(data.begin(), data.end(), 4) == data.begin());
     }
 
-    { // no match
-      std::deque<int> data;
-      data.resize(size);
-      std::fill(data.begin(), data.end(), 3);
+    { // single element - no match
+      std::deque<int> data = {3};
       assert(std::ranges::find(data, 4) == data.end());
       assert(std::ranges::find(data.begin(), data.end(), 4) == data.end());
     }
+
+    // many elements
+    for (auto size : {2, 3, 1023, 1024, 1025, 2047, 2048, 2049}) {
+      { // last element match
+        std::deque<int> data;
+        data.resize(size);
+        std::fill(data.begin(), data.end(), 3);
+        data[size - 1] = 4;
+        assert(std::ranges::find(data, 4) == data.end() - 1);
+        assert(std::ranges::find(data.begin(), data.end(), 4) == data.end() - 1);
+      }
+
+      { // second-last element match
+        std::deque<int> data;
+        data.resize(size);
+        std::fill(data.begin(), data.end(), 3);
+        data[size - 2] = 4;
+        assert(std::ranges::find(data, 4) == data.end() - 2);
+        assert(std::ranges::find(data.begin(), data.end(), 4) == data.end() - 2);
+      }
+
+      { // no match
+        std::deque<int> data;
+        data.resize(size);
+        std::fill(data.begin(), data.end(), 3);
+        assert(std::ranges::find(data, 4) == data.end());
+        assert(std::ranges::find(data.begin(), data.end(), 4) == data.end());
+      }
+    }
+  }
+  // join_view ranges adaptor
+  {
+    { // single element - match
+      int data[1][1] = {{4}};
+      auto joined    = std::views::join(data);
+      assert(std::ranges::find(joined, 4) == std::ranges::begin(joined));
+    }
+    { // single element - no match
+      // (reproducer for https://llvm.org/PR158279, where the iterator would never reach the end sentinel)
+      int data[1][1] = {{3}};
+      auto joined    = std::views::join(data);
+      assert(std::ranges::find(joined, 4) == std::ranges::end(joined));
+    }
+    { // several sub-arrays of size 1 - match
+      int data[3][1] = {{0}, {4}, {0}};
+      auto joined    = std::views::join(data);
+      assert(std::ranges::find(joined, 4) == std::next(std::ranges::begin(joined)));
+    }
+    { // several sub-arrays of size 2 - match in second element of an array
+      int data[3][2] = {{0, 0}, {0, 4}, {0, 0}};
+      auto joined    = std::views::join(data);
+      assert(std::ranges::find(joined, 4) == std::ranges::next(std::ranges::begin(joined), 3));
+    }
+    { // vector of empty vectors
+      std::vector<std::vector<int>> data = {{}, {}};
+      auto joined                        = std::views::join(data);
+      assert(std::ranges::find(joined, 4) == std::ranges::end(joined));
+    }
+    { // vector of variably sized vectors - match
+      std::vector<std::vector<int>> data = {{}, {}, {3, 4}, {}, {}};
+      auto joined                        = std::views::join(data);
+      assert(std::ranges::find(joined, 4) == std::ranges::next(std::ranges::begin(joined)));
+    }
+    { // vector of variably sized vectors - no match
+      std::vector<std::vector<int>> data = {{}, {}, {3, 5}, {}, {}};
+      auto joined                        = std::views::join(data);
+      assert(std::ranges::find(joined, 4) == std::ranges::end(joined));
+    }
   }
 }
 
 int main(int, char**) {
-  test_deque();
+  test_segmented_iterator_types();
   test();
   static_assert(test());
 



More information about the libcxx-commits mailing list