[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