[libcxx-commits] [libcxx] [libc++] Validate once and unwrap bounded iterators in binary-search … (PR #238169)

Michael Blume via libcxx-commits libcxx-commits at lists.llvm.org
Mon Nov 2 16:55:09 PST 2026


https://github.com/MichaelBlume created https://github.com/llvm/llvm-project/pull/238169

…algorithms.

>From dc9a63f500cf04b31fa8e0a66a6763351072349e Mon Sep 17 00:00:00 2001
From: Mike Blume <mikeblume at google.com>
Date: Mon, 2 Nov 2026 16:42:18 -0800
Subject: [PATCH] [libc++] Validate once and unwrap bounded iterators in
 binary-search algorithms.

---
 libcxx/include/__algorithm/equal_range.h      |  26 ++--
 libcxx/include/__algorithm/lower_bound.h      |   9 +-
 libcxx/include/__algorithm/unwrap_iter.h      |  36 +++++
 libcxx/include/__algorithm/upper_bound.h      |  10 +-
 libcxx/include/__iterator/bounded_iter.h      |  22 +++
 libcxx/include/__iterator/iterator_traits.h   |   6 +
 .../include/__iterator/static_bounded_iter.h  |  22 +++
 ...assert.binary_search.bounded_iter.pass.cpp | 101 +++++++++++++
 .../binary_search.bounded_iter.pass.cpp       | 137 ++++++++++++++++++
 9 files changed, 352 insertions(+), 17 deletions(-)
 create mode 100644 libcxx/test/libcxx/algorithms/alg.sorting/assert.binary_search.bounded_iter.pass.cpp
 create mode 100644 libcxx/test/libcxx/algorithms/alg.sorting/binary_search.bounded_iter.pass.cpp

diff --git a/libcxx/include/__algorithm/equal_range.h b/libcxx/include/__algorithm/equal_range.h
index 44715895d1fc5b..6171df94f0e842 100644
--- a/libcxx/include/__algorithm/equal_range.h
+++ b/libcxx/include/__algorithm/equal_range.h
@@ -14,6 +14,7 @@
 #include <__algorithm/half_positive.h>
 #include <__algorithm/iterator_operations.h>
 #include <__algorithm/lower_bound.h>
+#include <__algorithm/unwrap_iter.h>
 #include <__algorithm/upper_bound.h>
 #include <__config>
 #include <__functional/identity.h>
@@ -35,24 +36,29 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 template <class _AlgPolicy, class _Compare, class _Iter, class _Sent, class _Tp, class _Proj>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 pair<_Iter, _Iter>
 __equal_range(_Iter __first, _Sent __last, const _Tp& __value, _Compare&& __comp, _Proj&& __proj) {
-  auto __len  = _IterOps<_AlgPolicy>::distance(__first, __last);
-  _Iter __end = _IterOps<_AlgPolicy>::next(__first, __last);
+  auto __len    = _IterOps<_AlgPolicy>::distance(__first, __last);
+  _Iter __end   = _IterOps<_AlgPolicy>::next(__first, __last);
+  auto __ufirst = std::__unwrap_bounded_iter(__first, __len);
+  auto __uend   = std::__unwrap_bounded_iter(std::move(__end), 0);
   while (__len != 0) {
     auto __half_len = std::__half_positive(__len);
-    _Iter __mid     = _IterOps<_AlgPolicy>::next(__first, __half_len);
+    auto __mid      = _IterOps<_AlgPolicy>::next(__ufirst, __half_len);
     if (std::__invoke(__comp, std::__invoke(__proj, *__mid), __value)) {
-      __first = ++__mid;
+      __ufirst = ++__mid;
       __len -= __half_len + 1;
     } else if (std::__invoke(__comp, __value, std::__invoke(__proj, *__mid))) {
-      __end = __mid;
-      __len = __half_len;
+      __uend = __mid;
+      __len  = __half_len;
     } else {
-      _Iter __mp1 = __mid;
-      return pair<_Iter, _Iter>(std::__lower_bound<_AlgPolicy>(__first, __mid, __value, __comp, __proj),
-                                std::__upper_bound<_AlgPolicy>(++__mp1, __end, __value, __comp, __proj));
+      auto __mp1 = __mid;
+      return pair<_Iter, _Iter>(
+          std::__rewrap_bounded_iter(__first, std::__lower_bound<_AlgPolicy>(__ufirst, __mid, __value, __comp, __proj)),
+          std::__rewrap_bounded_iter(
+              __first, std::__upper_bound<_AlgPolicy>(++__mp1, __uend, __value, __comp, __proj)));
     }
   }
-  return pair<_Iter, _Iter>(__first, __first);
+  auto __res = std::__rewrap_bounded_iter(std::move(__first), std::move(__ufirst));
+  return pair<_Iter, _Iter>(__res, __res);
 }
 
 template <class _ForwardIterator, class _Tp, class _Compare>
diff --git a/libcxx/include/__algorithm/lower_bound.h b/libcxx/include/__algorithm/lower_bound.h
index 941a50c6c13fa2..f3c8a99ddadbfa 100644
--- a/libcxx/include/__algorithm/lower_bound.h
+++ b/libcxx/include/__algorithm/lower_bound.h
@@ -12,6 +12,7 @@
 #include <__algorithm/comp.h>
 #include <__algorithm/half_positive.h>
 #include <__algorithm/iterator_operations.h>
+#include <__algorithm/unwrap_iter.h>
 #include <__config>
 #include <__functional/identity.h>
 #include <__iterator/advance.h>
@@ -19,6 +20,7 @@
 #include <__iterator/iterator_traits.h>
 #include <__type_traits/invoke.h>
 #include <__type_traits/is_callable.h>
+#include <__utility/move.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -33,18 +35,19 @@ template <class _AlgPolicy, class _Iter, class _Type, class _Proj, class _Comp>
     typename iterator_traits<_Iter>::difference_type __len,
     _Comp& __comp,
     _Proj& __proj) {
+  auto __ufirst = std::__unwrap_bounded_iter(__first, __len);
   while (__len != 0) {
     auto __l2 = std::__half_positive(__len);
-    _Iter __m = __first;
+    auto __m  = __ufirst;
     _IterOps<_AlgPolicy>::advance(__m, __l2);
     if (std::__invoke(__comp, std::__invoke(__proj, *__m), __value)) {
-      __first = ++__m;
+      __ufirst = ++__m;
       __len -= __l2 + 1;
     } else {
       __len = __l2;
     }
   }
-  return __first;
+  return std::__rewrap_bounded_iter(std::move(__first), std::move(__ufirst));
 }
 
 // One-sided binary search, aka meta binary search, has been in the public domain for decades, and has the general
diff --git a/libcxx/include/__algorithm/unwrap_iter.h b/libcxx/include/__algorithm/unwrap_iter.h
index 74bcbf06700ba9..ac6d7981203fbc 100644
--- a/libcxx/include/__algorithm/unwrap_iter.h
+++ b/libcxx/include/__algorithm/unwrap_iter.h
@@ -74,6 +74,42 @@ _LIBCPP_CONSTEXPR _OrigIter __rewrap_iter(_OrigIter __orig_iter, _Iter __iter) _
   return _Impl::__rewrap(std::move(__orig_iter), std::move(__iter));
 }
 
+// __unwrap_bounded_iter(__i, __n) validates the range [__i, __i + __n) once and, if __i is one of libc++'s own
+// bounded iterators, returns a raw pointer to __i's element. Every other iterator type is returned unchanged.
+//
+// This lets an algorithm that only ever accesses elements inside a range it has validated (e.g. the bisection loop
+// of std::lower_bound) drop the per-step bounds checks of __bounded_iter and __static_bounded_iter: those checks are
+// already implied by the up-front one. Other iterators are deliberately left alone: they gain nothing from
+// unwrapping, and unwrapping a user-defined contiguous iterator could bypass checks of its own.
+//
+// __rewrap_bounded_iter(__orig_iter, __iter) converts the result of __unwrap_bounded_iter (or an iterator derived
+// from it) back to the type of __orig_iter. The result keeps __orig_iter's bounds. It is not re-checked: __iter must
+// lie inside the range that __unwrap_bounded_iter validated, which holds for positions an algorithm reaches by moving
+// within that range.
+
+template <class _Iter, class _Diff, __enable_if_t<!__is_bounded_iter<_Iter>::value, int> = 0>
+inline _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iter __unwrap_bounded_iter(_Iter __i, _Diff) _NOEXCEPT {
+  return __i;
+}
+
+template <class _Iter, class _Diff, __enable_if_t<__is_bounded_iter<_Iter>::value, int> = 0>
+inline _LIBCPP_CONSTEXPR_SINCE_CXX14 decltype(std::__to_address(std::declval<_Iter>()))
+__unwrap_bounded_iter(_Iter __i, _Diff __n) _NOEXCEPT {
+  // The iterator knows its own bounds, so it is the one validating the range.
+  __i.__validate_range(__n);
+  return std::__to_address(__i);
+}
+
+template <class _Iter, __enable_if_t<!__is_bounded_iter<_Iter>::value, int> = 0>
+inline _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iter __rewrap_bounded_iter(const _Iter&, _Iter __iter) _NOEXCEPT {
+  return __iter;
+}
+
+template <class _Iter, class _Ptr, __enable_if_t<__is_bounded_iter<_Iter>::value, int> = 0>
+inline _LIBCPP_CONSTEXPR_SINCE_CXX14 _Iter __rewrap_bounded_iter(const _Iter& __orig_iter, _Ptr __iter) _NOEXCEPT {
+  return __orig_iter.__unchecked_add(__iter - std::__to_address(__orig_iter));
+}
+
 _LIBCPP_END_NAMESPACE_STD
 
 _LIBCPP_POP_MACROS
diff --git a/libcxx/include/__algorithm/upper_bound.h b/libcxx/include/__algorithm/upper_bound.h
index d23fc13479836e..4cacf31a42bc60 100644
--- a/libcxx/include/__algorithm/upper_bound.h
+++ b/libcxx/include/__algorithm/upper_bound.h
@@ -12,6 +12,7 @@
 #include <__algorithm/comp.h>
 #include <__algorithm/half_positive.h>
 #include <__algorithm/iterator_operations.h>
+#include <__algorithm/unwrap_iter.h>
 #include <__config>
 #include <__functional/identity.h>
 #include <__functional/invoke.h>
@@ -35,18 +36,19 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 template <class _AlgPolicy, class _Compare, class _Iter, class _Sent, class _Tp, class _Proj>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 _Iter
 __upper_bound(_Iter __first, _Sent __last, const _Tp& __value, _Compare&& __comp, _Proj&& __proj) {
-  auto __len = _IterOps<_AlgPolicy>::distance(__first, __last);
+  auto __len    = _IterOps<_AlgPolicy>::distance(__first, __last);
+  auto __ufirst = std::__unwrap_bounded_iter(__first, __len);
   while (__len != 0) {
     auto __half_len = std::__half_positive(__len);
-    auto __mid      = _IterOps<_AlgPolicy>::next(__first, __half_len);
+    auto __mid      = _IterOps<_AlgPolicy>::next(__ufirst, __half_len);
     if (std::__invoke(__comp, __value, std::__invoke(__proj, *__mid)))
       __len = __half_len;
     else {
-      __first = ++__mid;
+      __ufirst = ++__mid;
       __len -= __half_len + 1;
     }
   }
-  return __first;
+  return std::__rewrap_bounded_iter(std::move(__first), std::move(__ufirst));
 }
 
 template <class _ForwardIterator, class _Tp, class _Compare>
diff --git a/libcxx/include/__iterator/bounded_iter.h b/libcxx/include/__iterator/bounded_iter.h
index 9e5d929c8f1911..109e5571236404 100644
--- a/libcxx/include/__iterator/bounded_iter.h
+++ b/libcxx/include/__iterator/bounded_iter.h
@@ -162,6 +162,25 @@ struct __bounded_iter {
     return __tmp;
   }
 
+  // Asserts that [*this, *this + __n) is a valid range within the bounds of this iterator. Algorithms use this to
+  // validate a whole range once before operating on the underlying iterators (see __unwrap_bounded_iter).
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __validate_range(difference_type __n) const _NOEXCEPT {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        __n >= 0, "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        __n <= __end_ - __current_,
+        "__bounded_iter::__validate_range: Attempt to use a range that extends past the end");
+  }
+
+  // Returns *this advanced by __n, keeping the same bounds, without checking the result. Only for positions inside a
+  // range that was already validated with __validate_range (see __rewrap_bounded_iter).
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __bounded_iter
+  __unchecked_add(difference_type __n) const _NOEXCEPT {
+    __bounded_iter __tmp(*this);
+    __tmp.__current_ += __n;
+    return __tmp;
+  }
+
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __bounded_iter& operator+=(difference_type __n) _NOEXCEPT {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
         __n >= __begin_ - __current_, "__bounded_iter::operator+=: Attempt to rewind an iterator past the start");
@@ -272,6 +291,9 @@ template <class _Iterator>
 struct __libcpp_is_contiguous_iterator<__bounded_iter<_Iterator> > : true_type {};
 #endif
 
+template <class _Iterator>
+struct __is_bounded_iter<__bounded_iter<_Iterator> > : true_type {};
+
 template <class _Iterator>
 struct pointer_traits<__bounded_iter<_Iterator> > {
   using pointer         = __bounded_iter<_Iterator>;
diff --git a/libcxx/include/__iterator/iterator_traits.h b/libcxx/include/__iterator/iterator_traits.h
index 4327317b23d463..597a4e40836a9a 100644
--- a/libcxx/include/__iterator/iterator_traits.h
+++ b/libcxx/include/__iterator/iterator_traits.h
@@ -398,6 +398,12 @@ struct __libcpp_is_contiguous_iterator : false_type {};
 template <class _Up>
 struct __libcpp_is_contiguous_iterator<_Up*> : true_type {};
 
+// __is_bounded_iter determines if an iterator is one of libc++'s own bounds-checked iterator wrappers
+// (__bounded_iter or __static_bounded_iter). Algorithms that validate a whole range up front can replace such
+// iterators with raw pointers, since every per-step check is implied by the up-front one.
+template <class _Iter>
+struct __is_bounded_iter : false_type {};
+
 template <class _Iter>
 class __wrap_iter;
 
diff --git a/libcxx/include/__iterator/static_bounded_iter.h b/libcxx/include/__iterator/static_bounded_iter.h
index a048073b5a897c..c211afa602376b 100644
--- a/libcxx/include/__iterator/static_bounded_iter.h
+++ b/libcxx/include/__iterator/static_bounded_iter.h
@@ -178,6 +178,25 @@ struct __static_bounded_iter {
     return __tmp;
   }
 
+  // Asserts that [*this, *this + __n) is a valid range within the bounds of this iterator. Algorithms use this to
+  // validate a whole range once before operating on the underlying iterators (see __unwrap_bounded_iter).
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void __validate_range(difference_type __n) const _NOEXCEPT {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        __n >= 0, "__static_bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        __n <= __end() - __current(),
+        "__static_bounded_iter::__validate_range: Attempt to use a range that extends past the end");
+  }
+
+  // Returns *this advanced by __n, keeping the same bounds, without checking the result. Only for positions inside a
+  // range that was already validated with __validate_range (see __rewrap_bounded_iter).
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter
+  __unchecked_add(difference_type __n) const _NOEXCEPT {
+    __static_bounded_iter __tmp(*this);
+    __tmp.__current() += __n;
+    return __tmp;
+  }
+
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 __static_bounded_iter& operator+=(difference_type __n) _NOEXCEPT {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
         __n >= __begin() - __current(),
@@ -301,6 +320,9 @@ template <class _Iterator, size_t _Size>
 struct __libcpp_is_contiguous_iterator<__static_bounded_iter<_Iterator, _Size> > : true_type {};
 #endif
 
+template <class _Iterator, size_t _Size>
+struct __is_bounded_iter<__static_bounded_iter<_Iterator, _Size> > : true_type {};
+
 template <class _Iterator, size_t _Size>
 struct pointer_traits<__static_bounded_iter<_Iterator, _Size> > {
   using pointer         = __static_bounded_iter<_Iterator, _Size>;
diff --git a/libcxx/test/libcxx/algorithms/alg.sorting/assert.binary_search.bounded_iter.pass.cpp b/libcxx/test/libcxx/algorithms/alg.sorting/assert.binary_search.bounded_iter.pass.cpp
new file mode 100644
index 00000000000000..3a526757575588
--- /dev/null
+++ b/libcxx/test/libcxx/algorithms/alg.sorting/assert.binary_search.bounded_iter.pass.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <algorithm>
+
+// The binary-search algorithms validate the whole input range once, up front, when they are given libc++'s bounded
+// iterators, and then search using the underlying pointers. Make sure invalid ranges are still diagnosed.
+
+// REQUIRES: can-test-hardening-assertions-fast
+// Continuing past a failed check is undefined behavior here.
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
+#include <__iterator/bounded_iter.h>
+#include <__iterator/static_bounded_iter.h>
+#include <algorithm>
+#include <functional>
+#include <vector>
+
+#include "check_assertion.h"
+#include "test_macros.h"
+
+int main(int, char**) {
+  int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+  // __bounded_iter: a reversed range.
+  {
+    auto first = std::__make_bounded_iter(array + 5, array, array + 10);
+    auto last  = std::__make_bounded_iter(array + 2, array, array + 10);
+    TEST_LIBCPP_ASSERT_FAILURE(std::lower_bound(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    TEST_LIBCPP_ASSERT_FAILURE(std::upper_bound(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    TEST_LIBCPP_ASSERT_FAILURE(std::equal_range(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    TEST_LIBCPP_ASSERT_FAILURE(std::binary_search(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    TEST_LIBCPP_ASSERT_FAILURE(std::lower_bound(first, last, 3, std::less<int>()),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+#if TEST_STD_VER >= 20
+    TEST_LIBCPP_ASSERT_FAILURE(std::ranges::lower_bound(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    TEST_LIBCPP_ASSERT_FAILURE(std::ranges::upper_bound(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    TEST_LIBCPP_ASSERT_FAILURE(std::ranges::equal_range(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    TEST_LIBCPP_ASSERT_FAILURE(std::ranges::binary_search(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+#endif
+  }
+
+  // __bounded_iter: `last` lies past the end of `first`'s bounds (e.g. it comes from a different range).
+  {
+    auto first = std::__make_bounded_iter(array, array, array + 5);
+    auto last  = std::__make_bounded_iter(array + 8, array, array + 10);
+    TEST_LIBCPP_ASSERT_FAILURE(std::lower_bound(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range that extends past the end");
+    TEST_LIBCPP_ASSERT_FAILURE(std::upper_bound(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range that extends past the end");
+    TEST_LIBCPP_ASSERT_FAILURE(std::equal_range(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range that extends past the end");
+    TEST_LIBCPP_ASSERT_FAILURE(std::binary_search(first, last, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range that extends past the end");
+  }
+
+  // __static_bounded_iter: a reversed range, and a range extending past the end.
+  {
+    auto first = std::__make_static_bounded_iter<10>(array + 5, array);
+    auto last  = std::__make_static_bounded_iter<10>(array + 2, array);
+    TEST_LIBCPP_ASSERT_FAILURE(
+        std::lower_bound(first, last, 3),
+        "__static_bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    TEST_LIBCPP_ASSERT_FAILURE(
+        std::equal_range(first, last, 3),
+        "__static_bounded_iter::__validate_range: Attempt to use a range with a negative length");
+
+    // `last` is valid in its own window, [array + 5, array + 10], but lies past the end of `short_first`'s.
+    auto short_first = std::__make_static_bounded_iter<5>(array, array);
+    auto other_last  = std::__make_static_bounded_iter<5>(array + 8, array + 5);
+    TEST_LIBCPP_ASSERT_FAILURE(
+        std::upper_bound(short_first, other_last, 3),
+        "__static_bounded_iter::__validate_range: Attempt to use a range that extends past the end");
+  }
+
+#ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
+  // The same through std::vector's iterators.
+  {
+    std::vector<int> v(array, array + 10);
+    TEST_LIBCPP_ASSERT_FAILURE(std::lower_bound(v.end(), v.begin(), 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+    TEST_LIBCPP_ASSERT_FAILURE(std::binary_search(v.begin() + 6, v.begin() + 1, 3),
+                               "__bounded_iter::__validate_range: Attempt to use a range with a negative length");
+  }
+#endif
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/algorithms/alg.sorting/binary_search.bounded_iter.pass.cpp b/libcxx/test/libcxx/algorithms/alg.sorting/binary_search.bounded_iter.pass.cpp
new file mode 100644
index 00000000000000..6a8b4f21cec9e8
--- /dev/null
+++ b/libcxx/test/libcxx/algorithms/alg.sorting/binary_search.bounded_iter.pass.cpp
@@ -0,0 +1,137 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <algorithm>
+
+// The binary-search algorithms unwrap libc++'s bounded iterators after validating the input range once. Check that
+// - only __bounded_iter and __static_bounded_iter are unwrapped,
+// - the results are correct and keep the bounds of the input iterators (including in constant expressions), and
+// - other users of iterator unwrapping are unaffected.
+
+#include <__algorithm/unwrap_iter.h>
+#include <__iterator/bounded_iter.h>
+#include <__iterator/static_bounded_iter.h>
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class Iter>
+using UnwrapBoundedT = decltype(std::__unwrap_bounded_iter(std::declval<Iter>(), std::ptrdiff_t()));
+
+// Only libc++'s own bounded iterators are unwrapped.
+static_assert(std::is_same<UnwrapBoundedT<std::__bounded_iter<int*> >, int*>::value, "");
+static_assert(std::is_same<UnwrapBoundedT<std::__bounded_iter<const int*> >, const int*>::value, "");
+static_assert(std::is_same<UnwrapBoundedT<std::__static_bounded_iter<int*, 4> >, int*>::value, "");
+static_assert(std::is_same<UnwrapBoundedT<int*>, int*>::value, "");
+static_assert(std::is_same<UnwrapBoundedT<std::__wrap_iter<int*> >, std::__wrap_iter<int*> >::value, "");
+static_assert(std::is_same<UnwrapBoundedT<std::reverse_iterator<std::reverse_iterator<int*> > >,
+                           std::reverse_iterator<std::reverse_iterator<int*> > >::value,
+              "");
+static_assert(std::is_same<UnwrapBoundedT<forward_iterator<int*> >, forward_iterator<int*> >::value, "");
+static_assert(std::is_same<UnwrapBoundedT<random_access_iterator<int*> >, random_access_iterator<int*> >::value, "");
+
+template <class Iter>
+TEST_CONSTEXPR_CXX20 void check_searches(Iter first, Iter last) {
+  // [first, last) holds {1, 2, 2, 2, 3, 5, 8}.
+  assert(std::lower_bound(first, last, 2) == first + 1);
+  assert(std::upper_bound(first, last, 2) == first + 4);
+  assert(std::lower_bound(first, last, 0) == first);
+  assert(std::upper_bound(first, last, 9) == last);
+  assert(std::equal_range(first, last, 2) == std::make_pair(first + 1, first + 4));
+  assert(std::equal_range(first, last, 4) == std::make_pair(first + 5, first + 5));
+  assert(std::binary_search(first, last, 5));
+  assert(!std::binary_search(first, last, 4));
+#if TEST_STD_VER >= 20
+  assert(std::ranges::lower_bound(first, last, 3) == first + 4);
+  assert(std::ranges::upper_bound(first, last, 3) == first + 5);
+  auto range = std::ranges::equal_range(first, last, 2);
+  assert(range.begin() == first + 1 && range.end() == first + 4);
+  assert(std::ranges::binary_search(first, last, 8));
+#endif
+
+  // The results are usable like any other iterator into the range: they can be dereferenced and moved within the
+  // original bounds.
+  Iter it = std::lower_bound(first, last, 3);
+  assert(*it == 3);
+  assert(*(it - 4) == 1);
+  assert(it + 3 == last);
+
+  // Empty ranges.
+  assert(std::lower_bound(first, first, 2) == first);
+  assert(std::equal_range(last, last, 2) == std::make_pair(last, last));
+}
+
+TEST_CONSTEXPR_CXX20 bool test() {
+  int array[] = {1, 2, 2, 2, 3, 5, 8};
+  int* b      = array;
+  int* e      = array + 7;
+
+  check_searches(std::__make_bounded_iter(b, b, e), std::__make_bounded_iter(e, b, e));
+  check_searches(std::__make_static_bounded_iter<7>(b, b), std::__make_static_bounded_iter<7>(e, b));
+
+  // A subrange of a larger bounded range.
+  int bigger[] = {0, 1, 2, 2, 2, 3, 5, 8, 9};
+  check_searches(std::__make_bounded_iter(bigger + 1, bigger, bigger + 9),
+                 std::__make_bounded_iter(bigger + 8, bigger, bigger + 9));
+
+  // Non-bounded iterators still work.
+  check_searches(b, e);
+  check_searches(random_access_iterator<int*>(b), random_access_iterator<int*>(e));
+  return true;
+}
+
+// An output iterator whose difference_type is void, as with std::iterator<std::output_iterator_tag, void, void, void,
+// void>. std::copy unwraps its output iterator, which must keep working.
+struct CountingOutputIterator {
+  using iterator_category = std::output_iterator_tag;
+  using value_type        = void;
+  using difference_type   = void;
+  using pointer           = void;
+  using reference         = void;
+
+  int* count;
+  CountingOutputIterator& operator*() { return *this; }
+  CountingOutputIterator& operator=(int) {
+    ++*count;
+    return *this;
+  }
+  CountingOutputIterator& operator++() { return *this; }
+  CountingOutputIterator operator++(int) { return *this; }
+};
+
+void test_other_unwrapping_users() {
+  int array[] = {1, 2, 2, 3};
+
+  int count = 0;
+  std::copy(array, array + 4, CountingOutputIterator{&count});
+  assert(count == 4);
+
+  // reverse_iterator<reverse_iterator<It>> has its own __unwrap_iter_impl specialization.
+  typedef std::reverse_iterator<std::reverse_iterator<int*> > DoublyReversed;
+  DoublyReversed first((std::reverse_iterator<int*>(array)));
+  DoublyReversed last((std::reverse_iterator<int*>(array + 4)));
+  assert(std::lower_bound(first, last, 2) - first == 1);
+  assert(std::upper_bound(first, last, 2) - first == 3);
+  assert(std::equal_range(first, last, 2) == std::make_pair(first + 1, first + 3));
+  assert(std::binary_search(first, last, 3));
+}
+
+int main(int, char**) {
+  test();
+#if TEST_STD_VER >= 20
+  static_assert(test());
+#endif
+  test_other_unwrapping_users();
+  return 0;
+}



More information about the libcxx-commits mailing list