[libcxx-commits] [libcxx] [libc++] Add compact bounded specialization for capacity_aware_iterator (PR #208271)
via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Jul 10 21:38:16 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: William Tran-Viet (smallp-o-p)
<details>
<summary>Changes</summary>
- By taking advantage of unused alignment bits for a pointer of type `T` and the static capacity known at compile time, we can make a compact bounded iterator who's size == `sizeof(T*)` by stuffing a counter inside those bits.
- This overload only applies in the following situation:
- Iter is a pointer
- The capacity is <= `(1 << (bit_width(alignof(T)) - 1)) - 1`. The extra -1 is required, because we need to represent the end position past the last element.
---
Full diff: https://github.com/llvm/llvm-project/pull/208271.diff
6 Files Affected:
- (modified) libcxx/include/__iterator/capacity_aware_iterator.h (+224)
- (modified) libcxx/test/libcxx/iterators/capacity_aware_iter/arithmetic.pass.cpp (+15-7)
- (modified) libcxx/test/libcxx/iterators/capacity_aware_iter/assert.pass.cpp (+55-3)
- (modified) libcxx/test/libcxx/iterators/capacity_aware_iter/comparison.pass.cpp (+5-2)
- (modified) libcxx/test/libcxx/iterators/capacity_aware_iter/dereference.pass.cpp (+15-6)
- (modified) libcxx/test/libcxx/iterators/capacity_aware_iter/types.compile.pass.cpp (+12)
``````````diff
diff --git a/libcxx/include/__iterator/capacity_aware_iterator.h b/libcxx/include/__iterator/capacity_aware_iterator.h
index a516feef60f36..36281bccdf2ec 100644
--- a/libcxx/include/__iterator/capacity_aware_iterator.h
+++ b/libcxx/include/__iterator/capacity_aware_iterator.h
@@ -11,6 +11,8 @@
#define _LIBCPP___CAPACITY_AWARE_ITERATOR_H
#include <__assert>
+#include <__bit/bit_cast.h>
+#include <__bit/countr.h>
#include <__compare/ordering.h>
#include <__compare/three_way_comparable.h>
#include <__config>
@@ -21,8 +23,11 @@
#include <__memory/pointer_traits.h>
#include <__type_traits/is_constructible.h>
#include <__type_traits/is_convertible.h>
+#include <__type_traits/is_pointer.h>
#include <__utility/move.h>
+#include <cstdint>
+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif
@@ -51,6 +56,7 @@ class __capacity_aware_iterator {
public:
static_assert(contiguous_iterator<_Iter>, "__capacity_aware_iterator can only be used with contiguous iterators");
+ constexpr static bool __can_track_count_ = false;
using iterator_category = iterator_traits<_Iter>::iterator_category;
using iterator_concept = contiguous_iterator_tag;
@@ -172,6 +178,224 @@ class __capacity_aware_iterator {
}
};
+template <class _Tp>
+consteval bool __range_fits_in_alignment(std::size_t __num_elems) {
+ std::size_t __bits = std::countr_zero(alignof(_Tp));
+
+ // Example: For alignof(T) == 4, we have two bits free, which has a range of 0-3. We need to
+ // reserve one for the end position, so __num_elems must be < 3.
+ std::size_t __allowed_range = (1 << __bits) - 1;
+ return __allowed_range > __num_elems;
+}
+
+// A specialization of capacity_aware_iterator where we store a runtime count of the current position inside
+// the unused bottom bits of a pointer to T. Only applies if capacity can fit inside those bits.
+template <class _Iter, class _Tag, size_t _RangeMaxElements>
+ requires(std::is_pointer_v<_Iter> && std::__range_fits_in_alignment<std::iter_value_t<_Iter>>(_RangeMaxElements))
+class __capacity_aware_iterator<_Iter, _Tag, _RangeMaxElements> {
+ constexpr static std::size_t __bits_ = std::countr_zero(alignof(std::iter_value_t<_Iter>));
+
+ union {
+ _Iter __ptr_;
+ std::uintptr_t __current_ : __bits_;
+ };
+
+ template <class, class, size_t>
+ friend class __capacity_aware_iterator;
+
+public:
+ constexpr static bool __can_track_count_ = false;
+
+ using iterator_category = iterator_traits<_Iter>::iterator_category;
+ using iterator_concept = contiguous_iterator_tag;
+ using difference_type = iter_difference_t<_Iter>;
+ using pointer = iterator_traits<_Iter>::pointer;
+ using reference = iter_reference_t<_Iter>;
+ using value_type = iter_value_t<_Iter>;
+
+ constexpr __capacity_aware_iterator()
+ requires is_default_constructible_v<_Iter>
+ = default;
+
+ template <typename _Iter2>
+ requires is_convertible_v<_Iter2, _Iter>
+ constexpr __capacity_aware_iterator(const __capacity_aware_iterator<_Iter2, _Tag, _RangeMaxElements>& __y) noexcept
+ : __ptr_(__y.__ptr_) {
+ if !consteval {
+ __current_ = __y.__current_;
+ }
+ }
+
+ template <class _It, class _Tag2, size_t _RangeMaxElems2>
+ friend constexpr auto __make_capacity_aware_iterator(_It __iter) noexcept;
+
+private:
+ constexpr explicit __capacity_aware_iterator(_Iter __iter) : __ptr_(std::move(__iter)) {
+ if !consteval {
+ __current_ = 0u;
+ }
+ }
+
+ constexpr _Iter __curr_iter() const {
+ if consteval {
+ return __ptr_;
+ }
+
+ return std::bit_cast<_Iter>(std::bit_cast<std::uintptr_t>(__ptr_) - __current_) + __current_;
+ }
+
+ constexpr void __update(difference_type __n) {
+ if consteval {
+ __ptr_ += __n;
+ } else {
+ __current_ += __n;
+ }
+ }
+
+public:
+ [[nodiscard]] constexpr decltype(auto) operator*() const noexcept {
+ if !consteval {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ __current_ < _RangeMaxElements,
+ "__capacity_aware_iterator::operator*: Attempt to dereference an iterator at the end");
+ }
+
+ return *(__curr_iter());
+ }
+
+ constexpr decltype(auto) operator->() const noexcept {
+ if !consteval {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ __current_ < _RangeMaxElements,
+ "__capacity_aware_iterator::operator->: Attempt to dereference an iterator at the end");
+ }
+
+ return __curr_iter();
+ }
+
+ constexpr __capacity_aware_iterator& operator++() noexcept {
+ if !consteval {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ __current_ != _RangeMaxElements,
+ "__capacity_aware_iterator::operator++: Attempt to advance an iterator past the end");
+ }
+
+ __update(1);
+
+ return *this;
+ }
+
+ constexpr __capacity_aware_iterator operator++(int) noexcept {
+ __capacity_aware_iterator __tmp(*this);
+ ++*this;
+ return __tmp;
+ }
+
+ constexpr __capacity_aware_iterator& operator--() noexcept {
+ if !consteval {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ __current_ != 0u, "__capacity_aware_iterator::operator--: Attempt to rewind an iterator past the start");
+ }
+
+ __update(-1);
+
+ return *this;
+ }
+
+ constexpr __capacity_aware_iterator operator--(int) noexcept {
+ __capacity_aware_iterator __tmp(*this);
+ --*this;
+ return __tmp;
+ }
+
+ constexpr __capacity_aware_iterator& operator+=(difference_type __n) noexcept {
+ if !consteval {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ (static_cast<difference_type>(__current_) + __n) >= 0,
+ "__capacity_aware_iterator::operator+=: Attempt to rewind an iterator past the start");
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ static_cast<std::size_t>(__current_ + __n) <= _RangeMaxElements,
+ "__capacity_aware_iterator::operator+=: Attempt to advance an iterator past the end");
+ }
+
+ __update(__n);
+
+ return *this;
+ }
+
+ constexpr __capacity_aware_iterator& operator-=(difference_type __n) noexcept {
+ if !consteval {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ (static_cast<difference_type>(__current_) - __n) >= 0,
+ "__capacity_aware_iterator::operator-=: Attempt to rewind an iterator past the start");
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ static_cast<std::size_t>(__current_ - __n) <= _RangeMaxElements,
+ "__capacity_aware_iterator::operator-=: Attempt to advance an iterator past the end");
+ }
+
+ __update(-__n);
+
+ return *this;
+ }
+
+ [[nodiscard]] constexpr decltype(auto) operator[](difference_type __n) const noexcept {
+ if !consteval {
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ (static_cast<difference_type>(__current_) + __n) >= 0,
+ "__capacity_aware_iterator::operator[]: Attempt to index an iterator past the start");
+ _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+ static_cast<std::size_t>(__current_ + __n) < _RangeMaxElements,
+ "__capacity_aware_iterator::operator[]: Attempt to index an iterator at or past the end");
+ }
+ return *(*this + __n);
+ }
+
+ friend constexpr bool
+ operator==(const __capacity_aware_iterator& __x, const __capacity_aware_iterator& __y) noexcept {
+ return __x.__curr_iter() == __y.__curr_iter();
+ }
+
+ friend constexpr auto
+ operator<=>(const __capacity_aware_iterator& __x, const __capacity_aware_iterator& __y) noexcept {
+ if constexpr (three_way_comparable_with<_Iter, _Iter, strong_ordering>) {
+ return __x.__curr_iter() <=> __y.__curr_iter();
+ } else {
+ if (__x.__curr_iter() < __y.__curr_iter()) {
+ return strong_ordering::less;
+ } else if (__x.__curr_iter() == __y.__curr_iter()) {
+ return strong_ordering::equal;
+ }
+ return strong_ordering::greater;
+ }
+ }
+
+ [[nodiscard]] friend constexpr __capacity_aware_iterator
+ operator+(const __capacity_aware_iterator& __i, difference_type __n) noexcept {
+ auto __tmp = __i;
+ __tmp += __n;
+ return __tmp;
+ }
+
+ [[nodiscard]] friend constexpr __capacity_aware_iterator
+ operator+(difference_type __n, const __capacity_aware_iterator& __i) noexcept {
+ auto __tmp = __i;
+ __tmp += __n;
+ return __tmp;
+ }
+
+ [[nodiscard]] friend constexpr __capacity_aware_iterator
+ operator-(const __capacity_aware_iterator& __i, difference_type __n) noexcept {
+ auto __tmp = __i;
+ __tmp -= __n;
+ return __tmp;
+ }
+
+ [[nodiscard]] friend constexpr difference_type
+ operator-(const __capacity_aware_iterator& __x, const __capacity_aware_iterator& __y) noexcept {
+ return difference_type(__x.__curr_iter() - __y.__curr_iter());
+ }
+};
+
template <class _It, class _Tag2, size_t _RangeMaxElems2>
_LIBCPP_HIDE_FROM_ABI constexpr auto __make_capacity_aware_iterator(_It __iter) noexcept {
return __capacity_aware_iterator<_It, _Tag2, _RangeMaxElems2>(__iter);
diff --git a/libcxx/test/libcxx/iterators/capacity_aware_iter/arithmetic.pass.cpp b/libcxx/test/libcxx/iterators/capacity_aware_iter/arithmetic.pass.cpp
index f4aa5c29790ff..d84648a75e8f7 100644
--- a/libcxx/test/libcxx/iterators/capacity_aware_iter/arithmetic.pass.cpp
+++ b/libcxx/test/libcxx/iterators/capacity_aware_iter/arithmetic.pass.cpp
@@ -33,14 +33,14 @@
#include "test_iterators.h"
#include "test_macros.h"
-template <typename Iter>
+template <typename Iter, typename Ty = int>
constexpr bool test() {
- int arr[] = {1, 2, 3, 4, 5, 6};
+ Ty arr[] = {1, 2, 3, 4, 5, 6};
constexpr size_t sz = std::size(arr);
using CapIter = std::__capacity_aware_iterator<Iter, decltype(arr), sz>;
- int* i = arr + 0;
+ Ty* i = arr + 0;
// operator++()
{
@@ -66,7 +66,8 @@ constexpr bool test() {
// operator--()
{
- CapIter iter = std::__make_capacity_aware_iterator<Iter, decltype(arr), sz>(Iter(i + 1));
+ CapIter iter = std::__make_capacity_aware_iterator<Iter, decltype(arr), sz>(Iter(i));
+ ++iter;
std::same_as<CapIter&> decltype(auto) res = --iter;
@@ -77,7 +78,8 @@ constexpr bool test() {
// operator--(int)
{
- CapIter iter = std::__make_capacity_aware_iterator<Iter, decltype(arr), sz>(Iter(i + 1));
+ CapIter iter = std::__make_capacity_aware_iterator<Iter, decltype(arr), sz>(Iter(i));
+ ++iter;
std::same_as<CapIter> decltype(auto) res = iter--;
@@ -121,7 +123,8 @@ constexpr bool test() {
// operator-=(difference_type)
{
- CapIter iter = std::__make_capacity_aware_iterator<Iter, decltype(arr), sz>(Iter(i + 2));
+ CapIter iter = std::__make_capacity_aware_iterator<Iter, decltype(arr), sz>(Iter(i));
+ iter += 2;
std::same_as<CapIter&> decltype(auto) res = iter -= 2;
@@ -132,7 +135,8 @@ constexpr bool test() {
// operator-(__capacity_aware_iterator, difference_type)
{
- CapIter iter = std::__make_capacity_aware_iterator<Iter, decltype(arr), sz>(Iter(i + 2));
+ CapIter iter = std::__make_capacity_aware_iterator<Iter, decltype(arr), sz>(Iter(i));
+ iter += 2;
std::same_as<CapIter> decltype(auto) res = iter - 2;
@@ -162,5 +166,9 @@ int main(int, char**) {
test<contiguous_iterator<int*>>();
static_assert(test<contiguous_iterator<int*>>());
+ // bounded overload
+ test<long long*, long long>();
+ static_assert(test<long long*, long long>());
+
return 0;
}
diff --git a/libcxx/test/libcxx/iterators/capacity_aware_iter/assert.pass.cpp b/libcxx/test/libcxx/iterators/capacity_aware_iter/assert.pass.cpp
index ceac20d549c34..889e67899d8a4 100644
--- a/libcxx/test/libcxx/iterators/capacity_aware_iter/assert.pass.cpp
+++ b/libcxx/test/libcxx/iterators/capacity_aware_iter/assert.pass.cpp
@@ -13,13 +13,64 @@
// struct __capacity_aware_iterator;
// Check assert failure if advancing, rewinding or indexing iterator past its maximum range size
+// Or if we're keeping track of the current position, if we're advancing, rewinding, indexing out of bounds.
#include <__iterator/capacity_aware_iterator.h>
#include <iterator>
#include "check_assertion.h"
-#include "test_iterators.h"
-#include "test_macros.h"
+
+// Specialization where we can fit a running count inside unused alignment bits.
+template <typename Iter>
+void test_bounded() {
+ int arr[] = {1, 2};
+ int* p = arr;
+
+ constexpr long sz = std::size(arr);
+
+ using CapIter = std::__capacity_aware_iterator<Iter, decltype(p), sz>;
+
+ CapIter it = std::__make_capacity_aware_iterator<Iter, decltype(p), sz>(Iter(arr));
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it--, "__capacity_aware_iterator::operator--: Attempt to rewind an iterator past the start");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it -= 1, "__capacity_aware_iterator::operator-=: Attempt to rewind an iterator past the start");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it += -1, "__capacity_aware_iterator::operator+=: Attempt to rewind an iterator past the start");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it += (sz + 1), "__capacity_aware_iterator::operator+=: Attempt to advance an iterator past the end");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it += -(sz + 1), "__capacity_aware_iterator::operator+=: Attempt to rewind an iterator past the start");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it -= (sz + 1), "__capacity_aware_iterator::operator-=: Attempt to rewind an iterator past the start");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it -= -(sz + 1), "__capacity_aware_iterator::operator-=: Attempt to advance an iterator past the end");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it[sz], "__capacity_aware_iterator::operator[]: Attempt to index an iterator at or past the end");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it[-sz], "__capacity_aware_iterator::operator[]: Attempt to index an iterator past the start");
+
+ ++it;
+ ++it;
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ *it, "__capacity_aware_iterator::operator*: Attempt to dereference an iterator at the end");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ it.operator->(), "__capacity_aware_iterator::operator->: Attempt to dereference an iterator at the end");
+
+ TEST_LIBCPP_ASSERT_FAILURE(
+ ++it, "__capacity_aware_iterator::operator++: Attempt to advance an iterator past the end");
+}
template <typename Iter>
void test() {
@@ -57,7 +108,8 @@ void test() {
}
int main(int, char**) {
- test<contiguous_iterator<int*>>();
+ test_bounded<int*>();
+ test<int*>();
return 0;
}
diff --git a/libcxx/test/libcxx/iterators/capacity_aware_iter/comparison.pass.cpp b/libcxx/test/libcxx/iterators/capacity_aware_iter/comparison.pass.cpp
index 34aa73bf4459c..149f37691eae0 100644
--- a/libcxx/test/libcxx/iterators/capacity_aware_iter/comparison.pass.cpp
+++ b/libcxx/test/libcxx/iterators/capacity_aware_iter/comparison.pass.cpp
@@ -30,9 +30,9 @@
#include "test_iterators.h"
#include "test_macros.h"
-template <typename Iter>
+template <typename Iter, typename Ty = int>
constexpr bool test() {
- int arr[] = {1, 2, 3, 4};
+ Ty arr[] = {1, 2, 3, 4};
constexpr long sz = std::size(arr);
using CapIter = std::__capacity_aware_iterator<Iter, decltype(arr), sz>;
@@ -113,5 +113,8 @@ int main(int, char**) {
test<contiguous_iterator<int*>>();
static_assert(test<contiguous_iterator<int*>>());
+ test<long long*, long long>();
+ static_assert(test<long long*, long long>());
+
return 0;
}
diff --git a/libcxx/test/libcxx/iterators/capacity_aware_iter/dereference.pass.cpp b/libcxx/test/libcxx/iterators/capacity_aware_iter/dereference.pass.cpp
index 42ab763896e2f..9048207d92ad2 100644
--- a/libcxx/test/libcxx/iterators/capacity_aware_iter/dereference.pass.cpp
+++ b/libcxx/test/libcxx/iterators/capacity_aware_iter/dereference.pass.cpp
@@ -25,14 +25,19 @@
#include "test_iterators.h"
#include "test_macros.h"
-struct Foo {
+struct alignas(4) Foo {
int x;
constexpr bool operator==(Foo const& other) const { return x == other.x; }
};
-template <typename Iter>
+struct alignas(8) Foo2 {
+ int x;
+ constexpr bool operator==(Foo2 const& other) const { return x == other.x; }
+};
+
+template <typename Iter, typename Ty = Foo>
constexpr bool test() {
- Foo arr[] = {Foo{1}, Foo{2}, Foo{3}, Foo{4}};
+ Ty arr[] = {Ty{1}, Ty{2}, Ty{3}, Ty{4}};
constexpr long sz = std::size(arr);
using CapIter = std::__capacity_aware_iterator<Iter, decltype(arr), sz>;
@@ -41,7 +46,7 @@ constexpr bool test() {
// operator[]
{
- std::same_as<Foo&> decltype(auto) res = it[0];
+ std::same_as<Ty&> decltype(auto) res = it[0];
ASSERT_NOEXCEPT(it[0]);
assert(res == arr[0]);
assert(&res == &arr[0]);
@@ -56,7 +61,7 @@ constexpr bool test() {
// operator*
{
- std::same_as<Foo&> decltype(auto) res = *it;
+ std::same_as<Ty&> decltype(auto) res = *it;
ASSERT_NOEXCEPT(*it);
assert(*it == arr[0]);
assert(&res == &arr[0]);
@@ -65,7 +70,7 @@ constexpr bool test() {
// operator->
{
- std::same_as<Foo*> decltype(auto) ptr = it.operator->();
+ std::same_as<Ty*> decltype(auto) ptr = it.operator->();
ASSERT_NOEXCEPT(it->x);
assert(ptr->x == 1);
assert(ptr == &arr[0]);
@@ -78,5 +83,9 @@ int main(int, char**) {
test<three_way_contiguous_iterator<Foo*>>();
static_assert(test<three_way_contiguous_iterator<Foo*>>());
+ // bounded overload
+ test<Foo2*, Foo2>();
+ static_assert(test<Foo2*, Foo2>());
+
return 0;
}
diff --git a/libcxx/test/libcxx/iterators/capacity_aware_iter/types.compile.pass.cpp b/libcxx/test/libcxx/iterators/capacity_aware_iter/types.compile.pass.cpp
index b3b72a0728ed7..e633dc67803f4 100644
--- a/libcxx/test/libcxx/iterators/capacity_aware_iter/types.compile.pass.cpp
+++ b/libcxx/test/libcxx/iterators/capacity_aware_iter/types.compile.pass.cpp
@@ -29,3 +29,15 @@ static_assert(std::is_same_v<CapIter::difference_type, std::iter_difference_t<It
static_assert(std::is_same_v<CapIter::reference, std::iter_reference_t<It>>);
static_assert(std::is_same_v<CapIter::reference, std::iter_reference_t<It>>);
static_assert(std::is_same_v<CapIter::value_type, std::iter_value_t<It>>);
+
+struct alignas(8) A {};
+// bounded overload
+using It2 = A*;
+using CapIter2 = std::__capacity_aware_iterator<It2, A[], 126>;
+
+static_assert(std::is_same_v<CapIter2::iterator_category, std::iterator_traits<It2>::iterator_category>);
+static_assert(std::is_same_v<CapIter2::iterator_concept, std::contiguous_iterator_tag>);
+static_assert(std::is_same_v<CapIter2::difference_type, std::iter_difference_t<It2>>);
+static_assert(std::is_same_v<CapIter2::reference, std::iter_reference_t<It2>>);
+static_assert(std::is_same_v<CapIter2::reference, std::iter_reference_t<It2>>);
+static_assert(std::is_same_v<CapIter2::value_type, std::iter_value_t<It2>>);
``````````
</details>
https://github.com/llvm/llvm-project/pull/208271
More information about the libcxx-commits
mailing list