[libcxx-commits] [libcxx] [libc++] Implement a type-safe iterator for optional (PR #154239)
William Tran-Viet via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Dec 2 18:30:24 PST 2025
https://github.com/smallp-o-p updated https://github.com/llvm/llvm-project/pull/154239
>From 6a1545ff95a398855039250302df6d6c2a9f901f Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Mon, 18 Aug 2025 20:09:40 -0400
Subject: [PATCH 1/3] Implement a type-safe iterator for optional
---
libcxx/include/CMakeLists.txt | 1 +
.../__iterator/upper_bounded_iterator.h | 174 ++++++++++++++++++
libcxx/include/module.modulemap.in | 1 +
libcxx/include/optional | 33 ++--
.../iterator.compile.pass.cpp | 28 ++-
5 files changed, 220 insertions(+), 17 deletions(-)
create mode 100644 libcxx/include/__iterator/upper_bounded_iterator.h
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 85a2f8ac8ec4b..587bb0dee1a08 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -514,6 +514,7 @@ set(files
__iterator/sortable.h
__iterator/static_bounded_iter.h
__iterator/unreachable_sentinel.h
+ __iterator/upper_bounded_iterator.h
__iterator/wrap_iter.h
__locale
__locale_dir/check_grouping.h
diff --git a/libcxx/include/__iterator/upper_bounded_iterator.h b/libcxx/include/__iterator/upper_bounded_iterator.h
new file mode 100644
index 0000000000000..1ae0d11ce379f
--- /dev/null
+++ b/libcxx/include/__iterator/upper_bounded_iterator.h
@@ -0,0 +1,174 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+/*
+ * __upper_bounded_iterator is an iterator that wraps an underlying iterator.
+ * It stores the underlying container type to prevent mixing iterators, and allow algorithms
+ * to optimize based on the underlying container type.
+ * It also stores the absolute maximum amount of elements the container can have, known at compile-time.
+ * As of writing, the only standard library containers which have this property are inplace_vector and optional.
+ */
+
+#ifndef _LIBCPP___ITERATOR_UPPER_BOUNDED_ITERATOR_H
+#define _LIBCPP___ITERATOR_UPPER_BOUNDED_ITERATOR_H
+
+#include <__compare/ordering.h>
+#include <__compare/three_way_comparable.h>
+#include <__config>
+#include <__cstddef/size_t.h>
+#include <__iterator/incrementable_traits.h>
+#include <__iterator/iterator_traits.h>
+#include <__memory/pointer_traits.h>
+#include <__type_traits/is_constructible.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+#if _LIBCPP_STD_VER >= 26
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Iter, class _Container, std::size_t _Max_Elements>
+class __upper_bounded_iterator {
+private:
+ _Iter __iter_;
+
+ friend _Container;
+
+public:
+ using iterator_category = iterator_traits<_Iter>::iterator_category;
+ using iterator_concept = _Iter::iterator_concept;
+ using value_type = iter_value_t<_Iter>;
+ using difference_type = iter_difference_t<_Iter>;
+ using reference = iter_reference_t<_Iter>;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __upper_bounded_iterator()
+ requires is_default_constructible_v<_Iter>
+ = default;
+
+ _LIBCPP_HIDE_FROM_ABI constexpr explicit __upper_bounded_iterator(_Iter __iter) : __iter_(std::move(__iter)) {}
+
+ _LIBCPP_HIDE_FROM_ABI constexpr _Iter __base() const noexcept(noexcept(_Iter(__iter_))) { return __iter_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr auto __max_elements() const noexcept { return _Max_Elements; }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator*() const { return *__iter_; }
+ _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator->() const
+ requires requires { __iter_.operator->(); }
+ {
+ return __iter_.operator->();
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __upper_bounded_iterator& operator++() {
+ ++__iter_;
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __upper_bounded_iterator operator++(int) {
+ __upper_bounded_iterator __tmp(*this);
+ ++*this;
+ return __tmp;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __upper_bounded_iterator& operator--() {
+ --__iter_;
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __upper_bounded_iterator operator--(int) {
+ __upper_bounded_iterator __tmp(*this);
+ --*this;
+ return __tmp;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __upper_bounded_iterator& operator+=(difference_type __x) {
+ __iter_ += __x;
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr __upper_bounded_iterator& operator-=(difference_type __x) {
+ __iter_ -= __x;
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator[](difference_type __n) const { return *(*this + __n); }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+ operator==(const __upper_bounded_iterator& __x, const __upper_bounded_iterator& __y) {
+ return __x.__iter_ == __y.__iter_;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr auto
+ operator<=>(const __upper_bounded_iterator& __x, const __upper_bounded_iterator& __y) {
+ if constexpr (three_way_comparable_with<_Iter, _Iter, strong_ordering>) {
+ return __x.__iter_ <=> __y.__iter_;
+ } else {
+ if (__x.__iter_ < __x.__iter_) {
+ return strong_ordering::less;
+ } else if (__x.__iter_ == __y.__iter_) {
+ return strong_ordering::equal;
+ }
+ return strong_ordering::greater;
+ }
+ }
+
+ template <class _Iter2>
+ _LIBCPP_HIDE_FROM_ABI friend constexpr auto
+ operator<=>(const __upper_bounded_iterator& __x,
+ const __upper_bounded_iterator<_Iter2, _Container, _Max_Elements> __y) {
+ if constexpr (three_way_comparable_with<_Iter, _Iter2, strong_ordering>) {
+ return __x.__iter_ <=> __y.__iter_;
+ } else {
+ if (__x.__iter_ < __x.__iter_) {
+ return strong_ordering::less;
+ } else if (__x.__iter_ == __y.__iter_) {
+ return strong_ordering::equal;
+ }
+ return strong_ordering::greater;
+ }
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr __upper_bounded_iterator
+ operator+(const __upper_bounded_iterator& __i, difference_type __n) {
+ auto __tmp = __i;
+ __tmp += __n;
+ return __tmp;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr __upper_bounded_iterator
+ operator+(difference_type __n, const __upper_bounded_iterator& __i) {
+ auto __tmp = __i;
+ __tmp += __n;
+ return __tmp;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI friend constexpr __upper_bounded_iterator
+ operator-(const __upper_bounded_iterator& __i, difference_type __n) {
+ return __i.__iter_ + __n;
+ }
+
+ template <class _Iter2>
+ _LIBCPP_HIDE_FROM_ABI friend constexpr difference_type
+ operator-(const __upper_bounded_iterator& __x,
+ const __upper_bounded_iterator<_Iter2, _Container, _Max_Elements>& __y) {
+ return difference_type(__x.__base() - __y.__base());
+ }
+};
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 26
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___ITERATOR_UPPER_BOUNDED_ITERATOR_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 5d2a92bf7905c..4b3669ec703ef 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1543,6 +1543,7 @@ module std [system] {
module sortable { header "__iterator/sortable.h" }
module static_bounded_iter { header "__iterator/static_bounded_iter.h" }
module unreachable_sentinel { header "__iterator/unreachable_sentinel.h" }
+ module upper_bounded_iterator { header "__iterator/upper_bounded_iterator.h" }
module wrap_iter { header "__iterator/wrap_iter.h" }
header "iterator"
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 7b979d3d6d577..911ab52dccdac 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -264,7 +264,6 @@ namespace std {
# include <__compare/three_way_comparable.h>
# include <__concepts/invocable.h>
# include <__config>
-# include <__cstddef/ptrdiff_t.h>
# include <__exception/exception.h>
# include <__format/range_format.h>
# include <__functional/hash.h>
@@ -272,6 +271,7 @@ namespace std {
# include <__functional/unary_function.h>
# include <__fwd/functional.h>
# include <__iterator/bounded_iter.h>
+# include <__iterator/upper_bounded_iterator.h>
# include <__iterator/wrap_iter.h>
# include <__memory/addressof.h>
# include <__memory/construct_at.h>
@@ -688,52 +688,53 @@ private:
using __pointer _LIBCPP_NODEBUG = add_pointer_t<remove_reference_t<_Tp>>;
using __const_pointer _LIBCPP_NODEBUG = add_pointer_t<const remove_reference_t<_Tp>>;
-public:
# if _LIBCPP_STD_VER >= 26
+ template <typename _Underlying>
+ using __iter _LIBCPP_NODEBUG = __upper_bounded_iterator<_Underlying, __optional_iterator, 1>;
+
+public:
# ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
- using iterator = __bounded_iter<__wrap_iter<__pointer>>;
- using const_iterator = __bounded_iter<__wrap_iter<__const_pointer>>;
+ using iterator = __iter<__bounded_iter<__wrap_iter<__pointer>>>;
+ using const_iterator = __iter<__bounded_iter<__wrap_iter<__const_pointer>>>;
# else
- using iterator = __wrap_iter<__pointer>;
- using const_iterator = __wrap_iter<__const_pointer>;
+ using iterator = __iter<__wrap_iter<__pointer>>;
+ using const_iterator = __iter<__wrap_iter<__const_pointer>>;
# endif
// [optional.iterators], iterator support
_LIBCPP_HIDE_FROM_ABI constexpr iterator begin() noexcept {
auto& __derived_self = static_cast<optional<_Tp>&>(*this);
- auto __ptr = [&__derived_self]() {
+ auto __ptr = [&__derived_self] {
if constexpr (is_lvalue_reference_v<_Tp>) {
return __derived_self.has_value() ? std::addressof(__derived_self.__get()) : nullptr;
}
return std::addressof(__derived_self.__get());
}();
-
# ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
- return std::__make_bounded_iter(
+ return iterator(std::__make_bounded_iter(
__wrap_iter<__pointer>(__ptr),
__wrap_iter<__pointer>(__ptr),
- __wrap_iter<__pointer>(__ptr) + (__derived_self.has_value() ? 1 : 0));
+ __wrap_iter<__pointer>(__ptr) + (__derived_self.has_value() ? 1 : 0)));
# else
- return iterator(__ptr);
+ return iterator(__wrap_iter<__pointer>(__ptr));
# endif
}
_LIBCPP_HIDE_FROM_ABI constexpr const_iterator begin() const noexcept {
auto& __derived_self = static_cast<const optional<_Tp>&>(*this);
- auto* __ptr = [&__derived_self]() {
+ auto __ptr = [&__derived_self] {
if constexpr (is_lvalue_reference_v<_Tp>) {
return __derived_self.has_value() ? std::addressof(__derived_self.__get()) : nullptr;
}
return std::addressof(__derived_self.__get());
}();
-
# ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
- return std::__make_bounded_iter(
+ return const_iterator(std::__make_bounded_iter(
__wrap_iter<__const_pointer>(__ptr),
__wrap_iter<__const_pointer>(__ptr),
- __wrap_iter<__const_pointer>(__ptr) + (__derived_self.has_value() ? 1 : 0));
+ __wrap_iter<__const_pointer>(__ptr) + (__derived_self.has_value() ? 1 : 0)));
# else
- return const_iterator(__ptr);
+ return const_iterator(__wrap_iter<__const_pointer>(__ptr));
# endif
}
diff --git a/libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp b/libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp
index b604579e43557..424367b4f10b2 100644
--- a/libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp
@@ -7,13 +7,14 @@
//===----------------------------------------------------------------------===//
// REQUIRES: std-at-least-c++26
-
+// UNSUPPORTED: generic-hardening
// <optional>
// template <class T> class optional::iterator;
// template <class T> class optional::const_iterator;
#include <optional>
+#include <type_traits>
template <typename T>
concept has_iterator_aliases = requires {
@@ -27,3 +28,28 @@ static_assert(has_iterator_aliases<std::optional<int&>>);
static_assert(has_iterator_aliases<std::optional<const int&>>);
static_assert(!has_iterator_aliases<std::optional<int (&)[1]>>);
static_assert(!has_iterator_aliases<std::optional<int (&)()>>);
+
+using Iter1 = std::optional<int>::iterator;
+using Iter2 = std::optional<double>::iterator;
+using Iter3 = std::optional<int>::const_iterator;
+using Iter4 = std::optional<double>::const_iterator;
+
+static_assert(std::is_convertible_v<Iter1, Iter1>);
+static_assert(!std::is_convertible_v<Iter1, Iter2>);
+static_assert(!std::is_convertible_v<Iter1, Iter3>);
+static_assert(!std::is_convertible_v<Iter1, Iter4>);
+
+static_assert(std::is_convertible_v<Iter2, Iter2>);
+static_assert(!std::is_convertible_v<Iter2, Iter1>);
+static_assert(!std::is_convertible_v<Iter2, Iter3>);
+static_assert(!std::is_convertible_v<Iter2, Iter4>);
+
+static_assert(std::is_convertible_v<Iter3, Iter3>);
+static_assert(!std::is_convertible_v<Iter3, Iter1>);
+static_assert(!std::is_convertible_v<Iter3, Iter2>);
+static_assert(!std::is_convertible_v<Iter3, Iter4>);
+
+static_assert(std::is_convertible_v<Iter4, Iter4>);
+static_assert(!std::is_convertible_v<Iter4, Iter1>);
+static_assert(!std::is_convertible_v<Iter4, Iter2>);
+static_assert(!std::is_convertible_v<Iter4, Iter3>);
>From d5927a7d9f2b909d36dfdfd57bc3ef6955b92e6b Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Mon, 18 Aug 2025 20:39:34 -0400
Subject: [PATCH 2/3] Update iterator tests
---
.../iterator.compile.pass.cpp | 2 +-
.../optional.iterator/iterator.pass.cpp | 24 +++++
.../iterator_compare.pass.cpp | 99 +++++++++++++++++++
3 files changed, 124 insertions(+), 1 deletion(-)
create mode 100644 libcxx/test/std/utilities/optional/optional.iterator/iterator_compare.pass.cpp
diff --git a/libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp b/libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp
index 424367b4f10b2..442195bdcb948 100644
--- a/libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp
+++ b/libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
// REQUIRES: std-at-least-c++26
-// UNSUPPORTED: generic-hardening
+
// <optional>
// template <class T> class optional::iterator;
diff --git a/libcxx/test/std/utilities/optional/optional.iterator/iterator.pass.cpp b/libcxx/test/std/utilities/optional/optional.iterator/iterator.pass.cpp
index 671fac35e732a..bb398b0fae4ac 100644
--- a/libcxx/test/std/utilities/optional/optional.iterator/iterator.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.iterator/iterator.pass.cpp
@@ -86,6 +86,30 @@ constexpr bool test() {
assert(*(val.begin()) == v);
}
+ // [container.reqmts] operator-
+ {
+ std::optional<T> val(v);
+ auto it1 = val.begin();
+ auto it2 = val.begin();
+ auto it3 = val.end();
+
+ auto cit1 = std::as_const(val).begin();
+ auto cit2 = std::as_const(val).begin();
+ auto cit3 = std::as_const(val).end();
+
+ assert(it1 - it2 == 0);
+ assert(cit1 - cit2 == 0);
+ assert(it1 - cit1 == 0);
+ assert(it3 - it1 == 1);
+ assert(it1 - it3 == -1);
+
+ assert(cit3 - cit1 == 1);
+ assert(cit1 - cit3 == -1);
+ assert(cit3 - cit3 == 0);
+ assert(cit3 - it1 == 1);
+ assert(it1 - cit3 == -1);
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/optional/optional.iterator/iterator_compare.pass.cpp b/libcxx/test/std/utilities/optional/optional.iterator/iterator_compare.pass.cpp
new file mode 100644
index 0000000000000..16b74ade9d82e
--- /dev/null
+++ b/libcxx/test/std/utilities/optional/optional.iterator/iterator_compare.pass.cpp
@@ -0,0 +1,99 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++26
+
+// <optional>
+
+// template <class T> class optional::iterator;
+// template <class T> class optional::const_iterator;
+
+#include <cassert>
+#include <compare>
+#include <concepts>
+#include <optional>
+#include <type_traits>
+#include <utility>
+
+template<typename T>
+constexpr bool test() {
+ using Opt = std::optional<T>;
+ using I = Opt::iterator;
+ using CI = Opt::const_iterator;
+
+ static_assert(std::three_way_comparable<I>);
+ static_assert(std::three_way_comparable<CI>);
+
+ std::remove_reference_t<T> t{};
+ Opt opt{t};
+
+ // [container.reqmts] tests for comparison operators of optional::iterator and optional::const_iterator
+ auto it1 = opt.begin();
+
+ {
+ auto it2 = opt.begin();
+ assert(it1 == it2);
+ assert(!(it1 != it2));
+
+ static_assert(std::same_as<decltype(it1 <=> it2), std::strong_ordering>);
+ assert(it1 <=> it2 == std::strong_ordering::equal);
+ }
+
+ {
+ auto it3 = opt.end();
+ assert(it1 != it3);
+ assert(it1 <= it3);
+ assert(it1 < it3);
+ assert(it3 >= it1);
+ assert(it3 > it1);
+
+ assert(it1 <=> it3 == std::strong_ordering::less);
+ assert(it3 <=> it1 == std::strong_ordering::greater);
+ }
+
+ auto cit1 = std::as_const(opt).begin();
+
+ {
+ auto cit2 = std::as_const(opt).begin();
+ assert(cit1 == cit2);
+ assert(!(cit1 != cit2));
+
+ static_assert(std::same_as<decltype(cit1 <=> cit2), std::strong_ordering>);
+ assert(cit1 <=> cit2 == std::strong_ordering::equal);
+ }
+
+ {
+ auto cit3 = std::as_const(opt).end();
+
+ assert(cit1 <= cit3);
+ assert(cit1 < cit3);
+ assert(cit3 >= cit1);
+ assert(cit3 > cit1);
+
+ assert(cit1 <=> cit3 == std::strong_ordering::less);
+ assert(cit3 <=> cit1 == std::strong_ordering::greater);
+ }
+
+ return true;
+}
+
+
+constexpr bool test() {
+ test<int>();
+ test<char>();
+ test<int&>();
+
+ return true;
+}
+
+int main(int, char**) {
+ assert(test());
+ static_assert(test());
+
+ return 0;
+}
>From a5bb0c424168076c413e6ebc354d86ab2b379262 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Tue, 2 Dec 2025 21:30:14 -0500
Subject: [PATCH 3/3] Formatting
---
.../optional/optional.iterator/iterator_compare.pass.cpp | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/libcxx/test/std/utilities/optional/optional.iterator/iterator_compare.pass.cpp b/libcxx/test/std/utilities/optional/optional.iterator/iterator_compare.pass.cpp
index 16b74ade9d82e..958519d4c805c 100644
--- a/libcxx/test/std/utilities/optional/optional.iterator/iterator_compare.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.iterator/iterator_compare.pass.cpp
@@ -20,11 +20,11 @@
#include <type_traits>
#include <utility>
-template<typename T>
+template <typename T>
constexpr bool test() {
using Opt = std::optional<T>;
- using I = Opt::iterator;
- using CI = Opt::const_iterator;
+ using I = Opt::iterator;
+ using CI = Opt::const_iterator;
static_assert(std::three_way_comparable<I>);
static_assert(std::three_way_comparable<CI>);
@@ -82,7 +82,6 @@ constexpr bool test() {
return true;
}
-
constexpr bool test() {
test<int>();
test<char>();
More information about the libcxx-commits
mailing list