[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