Skip to content

[libc++] Implement P3168R2: Give optional range support #149441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

smallp-o-p
Copy link
Contributor

Resolves #105430

  • Implement all required pieces of P3168R2
  • Leverage existing wrap_iter class to implement the optional iterator type. Hardening has not been added, do we want that for optional?
  • Update documentation to match

@smallp-o-p smallp-o-p requested a review from a team as a code owner July 18, 2025 03:00
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jul 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 18, 2025

@llvm/pr-subscribers-libcxx

Author: William Tran-Viet (smallp-o-p)

Changes

Resolves #105430

  • Implement all required pieces of P3168R2
  • Leverage existing wrap_iter class to implement the optional iterator type. Hardening has not been added, do we want that for optional?
  • Update documentation to match

Patch is 20.15 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/149441.diff

12 Files Affected:

  • (modified) libcxx/docs/FeatureTestMacroTable.rst (+1-1)
  • (modified) libcxx/docs/ReleaseNotes/21.rst (+1-1)
  • (modified) libcxx/docs/Status/Cxx2cPapers.csv (+1-1)
  • (modified) libcxx/include/__iterator/wrap_iter.h (+2)
  • (modified) libcxx/include/optional (+49)
  • (modified) libcxx/include/version (+1-1)
  • (modified) libcxx/modules/std/optional.inc (+10-1)
  • (modified) libcxx/test/libcxx/transitive_includes/cxx26.csv (+47)
  • (modified) libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp (+5-11)
  • (modified) libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp (+5-11)
  • (added) libcxx/test/std/utilities/optional/iterator.pass.cpp (+124)
  • (modified) libcxx/utils/generate_feature_test_macro_components.py (-1)
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 61805726a4ff0..5114ae59d7c67 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -480,7 +480,7 @@ Status
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_not_fn``                                       ``202306L``
     ---------------------------------------------------------- -----------------
-    ``__cpp_lib_optional_range_support``                       *unimplemented*
+    ``__cpp_lib_optional_range_support``                       ``202406L``
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_out_ptr``                                      ``202311L``
     ---------------------------------------------------------- -----------------
diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst
index 6f18b61284f49..b8fd5e7221251 100644
--- a/libcxx/docs/ReleaseNotes/21.rst
+++ b/libcxx/docs/ReleaseNotes/21.rst
@@ -53,7 +53,7 @@ Implemented Papers
 - P2711R1: Making multi-param constructors of ``views`` ``explicit`` (`Github <https://github.com/llvm/llvm-project/issues/105252>`__)
 - P2770R0: Stashing stashing ``iterators`` for proper flattening (`Github <https://github.com/llvm/llvm-project/issues/105250>`__)
 - P2655R3: ``common_reference_t`` of ``reference_wrapper`` Should Be a Reference Type (`Github <https://github.com/llvm/llvm-project/issues/105260>`__)
-
+- P3168R2 Give ``std::optional`` Range Support (`Github <https://github.com/llvm/llvm-project/issues/105430>`__)
 Improvements and New Features
 -----------------------------
 
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index febb0c176f9c4..95f0d08fdde1c 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -66,7 +66,7 @@
 "`P2747R2 <https://wg21.link/P2747R2>`__","``constexpr`` placement new","2024-06 (St. Louis)","|Complete|","20",""
 "`P2997R1 <https://wg21.link/P2997R1>`__","Removing the common reference requirement from the indirectly invocable concepts","2024-06 (St. Louis)","|Complete|","19","Implemented as a DR against C++20. (MSVC STL and libstdc++ will do the same.)"
 "`P2389R2 <https://wg21.link/P2389R2>`__","``dextents`` Index Type Parameter","2024-06 (St. Louis)","|Complete|","19",""
-"`P3168R2 <https://wg21.link/P3168R2>`__","Give ``std::optional`` Range Support","2024-06 (St. Louis)","","",""
+"`P3168R2 <https://wg21.link/P3168R2>`__","Give ``std::optional`` Range Support","2024-06 (St. Louis)","","21","|Complete|"
 "`P3217R0 <https://wg21.link/P3217R0>`__","Adjoints to 'Enabling list-initialization for algorithms': find_last","2024-06 (St. Louis)","","",""
 "`P2985R0 <https://wg21.link/P2985R0>`__","A type trait for detecting virtual base classes","2024-06 (St. Louis)","|Complete|","20",""
 "`P0843R14 <https://wg21.link/P0843R14>`__","``inplace_vector``","2024-06 (St. Louis)","","",""
diff --git a/libcxx/include/__iterator/wrap_iter.h b/libcxx/include/__iterator/wrap_iter.h
index 2b5bc489dd44c..7610586ddecbb 100644
--- a/libcxx/include/__iterator/wrap_iter.h
+++ b/libcxx/include/__iterator/wrap_iter.h
@@ -117,6 +117,8 @@ class __wrap_iter {
   friend class span;
   template <class _Tp, size_t _Size>
   friend struct array;
+  template <class _Tp>
+  friend class optional;
 };
 
 template <class _Iter1>
diff --git a/libcxx/include/optional b/libcxx/include/optional
index e81bff50daad6..1292d969fc9ed 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -20,6 +20,11 @@ namespace std {
   template <class T>
     class optional;
 
+  template<class T>
+   constexpr bool ranges::enable_view<optional<T>> = true;
+  template<class T>
+   constexpr auto format_kind<optional<T>> = range_format::disabled;
+
   template<class T>
     concept is-derived-from-optional = requires(const T& t) {       // exposition only
       []<class U>(const optional<U>&){ }(t);
@@ -102,6 +107,8 @@ namespace std {
   class optional {
   public:
     using value_type = T;
+    using iterator               = implementation-defined; // see [optional.iterators]
+    using const_iterator         = implementation-defined; // see [optional.iterators]
 
     // [optional.ctor], constructors
     constexpr optional() noexcept;
@@ -135,6 +142,12 @@ namespace std {
     // [optional.swap], swap
     void swap(optional &) noexcept(see below ); // constexpr in C++20
 
+    // [optional.iterators], iterator support
+    constexpr iterator begin() noexcept;
+    constexpr const_iterator begin() const noexcept;
+    constexpr iterator end() noexcept;
+    constexpr const_iterator end() const noexcept;
+
     // [optional.observe], observers
     constexpr T const *operator->() const noexcept;
     constexpr T *operator->() noexcept;
@@ -233,6 +246,13 @@ namespace std {
 #  include <initializer_list>
 #  include <version>
 
+#  if _LIBCPP_STD_VER >= 26
+#    include <__cstddef/ptrdiff_t.h>
+#    include <__format/range_format.h>
+#    include <__iterator/wrap_iter.h>
+#    include <__ranges/enable_view.h>
+#  endif
+
 // standard-mandated includes
 
 // [optional.syn]
@@ -567,6 +587,14 @@ using __optional_sfinae_assign_base_t _LIBCPP_NODEBUG =
 template <class _Tp>
 class optional;
 
+#    if _LIBCPP_STD_VER >= 26
+template <class _Tp>
+constexpr bool ranges::enable_view<optional<_Tp>> = true;
+
+template <class _Tp>
+constexpr range_format format_kind<optional<_Tp>> = range_format::disabled;
+#    endif
+
 #    if _LIBCPP_STD_VER >= 20
 
 template <class _Tp>
@@ -586,8 +614,17 @@ class _LIBCPP_DECLSPEC_EMPTY_BASES optional
       private __optional_sfinae_assign_base_t<_Tp> {
   using __base _LIBCPP_NODEBUG = __optional_move_assign_base<_Tp>;
 
+#    if _LIBCPP_STD_VER >= 26
+  using pointer       = std::add_pointer_t<std::remove_cvref_t<_Tp>>;
+  using const_pointer = std::add_pointer_t<const std::remove_cvref_t<_Tp>>;
+#    endif
+
 public:
   using value_type = _Tp;
+#    if _LIBCPP_STD_VER >= 26
+  using iterator       = __wrap_iter<pointer>;
+  using const_iterator = __wrap_iter<const_pointer>;
+#    endif
 
   using __trivially_relocatable _LIBCPP_NODEBUG =
       conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, optional, void>;
@@ -792,6 +829,18 @@ public:
     }
   }
 
+#    if _LIBCPP_STD_VER >= 26
+  // [optional.iterators], iterator support
+  _LIBCPP_HIDE_FROM_ABI constexpr iterator begin() noexcept { return iterator(std::addressof(this->__get())); }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator begin() const noexcept {
+    return const_iterator(std::addressof(this->__get()));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept { return begin() + this->has_value(); }
+  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator end() const noexcept { return begin() + this->has_value(); }
+#    endif
+
   _LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<value_type const> operator->() const noexcept {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value");
     return std::addressof(this->__get());
diff --git a/libcxx/include/version b/libcxx/include/version
index d98049bd57046..88bceb9f5dc01 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -585,7 +585,7 @@ __cpp_lib_void_t                                        201411L <type_traits>
 # define __cpp_lib_mdspan                               202406L
 # undef  __cpp_lib_not_fn
 # define __cpp_lib_not_fn                               202306L
-// # define __cpp_lib_optional_range_support               202406L
+# define __cpp_lib_optional_range_support               202406L
 # undef  __cpp_lib_out_ptr
 # define __cpp_lib_out_ptr                              202311L
 // # define __cpp_lib_philox_engine                        202406L
diff --git a/libcxx/modules/std/optional.inc b/libcxx/modules/std/optional.inc
index 0f812bc0e24a4..9ee51117277ce 100644
--- a/libcxx/modules/std/optional.inc
+++ b/libcxx/modules/std/optional.inc
@@ -10,7 +10,12 @@
 export namespace std {
   // [optional.optional], class template optional
   using std::optional;
-
+#if _LIBCPP_STD_VER >= 26
+  // [optional.iterators], iterator support
+  namespace ranges {
+    using std::ranges::enable_view;
+  }
+#endif
   // [optional.nullopt], no-value state indicator
   using std::nullopt;
   using std::nullopt_t;
@@ -18,6 +23,10 @@ export namespace std {
   // [optional.bad.access], class bad_optional_access
   using std::bad_optional_access;
 
+#if _LIBCPP_STD_VER >= 26
+  using std::format_kind;
+#endif
+
   // [optional.relops], relational operators
   using std::operator==;
   using std::operator!=;
diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index 5f906338f4b7c..7d383d5c8e819 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -2,6 +2,7 @@ algorithm cctype
 algorithm climits
 algorithm compare
 algorithm cstdint
+algorithm cstdio
 algorithm cstring
 algorithm ctime
 algorithm cwchar
@@ -11,6 +12,8 @@ algorithm iosfwd
 algorithm limits
 algorithm optional
 algorithm ratio
+algorithm stdexcept
+algorithm string_view
 algorithm tuple
 algorithm version
 any cstdint
@@ -330,26 +333,32 @@ flat_map cctype
 flat_map climits
 flat_map compare
 flat_map cstdint
+flat_map cstdio
 flat_map cstring
 flat_map cwchar
 flat_map cwctype
 flat_map initializer_list
+flat_map iosfwd
 flat_map limits
 flat_map optional
 flat_map stdexcept
+flat_map string_view
 flat_map tuple
 flat_map version
 flat_set cctype
 flat_set climits
 flat_set compare
 flat_set cstdint
+flat_set cstdio
 flat_set cstring
 flat_set cwchar
 flat_set cwctype
 flat_set initializer_list
+flat_set iosfwd
 flat_set limits
 flat_set optional
 flat_set stdexcept
+flat_set string_view
 flat_set tuple
 flat_set version
 format array
@@ -417,13 +426,16 @@ functional array
 functional cctype
 functional compare
 functional cstdint
+functional cstdio
 functional cstring
 functional cwchar
 functional cwctype
 functional initializer_list
+functional iosfwd
 functional limits
 functional optional
 functional stdexcept
+functional string_view
 functional tuple
 functional typeinfo
 functional unordered_map
@@ -623,13 +635,16 @@ locale version
 map cctype
 map compare
 map cstdint
+map cstdio
 map cstring
 map cwchar
 map cwctype
 map initializer_list
+map iosfwd
 map limits
 map optional
 map stdexcept
+map string_view
 map tuple
 map version
 mdspan array
@@ -673,22 +688,36 @@ mutex typeinfo
 mutex version
 new version
 numbers version
+numeric cctype
 numeric climits
 numeric compare
 numeric cstdint
+numeric cstdio
 numeric cstring
 numeric ctime
+numeric cwchar
+numeric cwctype
 numeric initializer_list
+numeric iosfwd
 numeric limits
 numeric optional
 numeric ratio
+numeric stdexcept 
+numeric string_view
 numeric tuple
 numeric version
+optional cctype
 optional compare
 optional cstdint
+optional cstdio
 optional cstring
+optional cwchar
+optional cwctype
 optional initializer_list
+optional iosfwd
 optional limits
+optional stdexcept
+optional string_view
 optional version
 ostream array
 ostream bitset
@@ -806,6 +835,7 @@ ranges limits
 ranges optional
 ranges span
 ranges stdexcept
+ranges string_view
 ranges tuple
 ranges variant
 ranges version
@@ -851,12 +881,16 @@ semaphore version
 set cctype
 set compare
 set cstdint
+set cstdio
 set cstring
 set cwchar
 set cwctype
 set initializer_list
+set iosfwd
 set limits
 set optional
+set stdexcept 
+set string_view
 set tuple
 set version
 shared_mutex cerrno
@@ -1091,21 +1125,34 @@ typeindex typeinfo
 typeindex version
 typeinfo cstdint
 typeinfo version
+unordered_map cctype
 unordered_map compare
 unordered_map cstdint
+unordered_map cstdio
 unordered_map cstring
+unordered_map cwchar
+unordered_map cwctype
 unordered_map initializer_list
+unordered_map iosfwd
 unordered_map limits
 unordered_map optional
 unordered_map stdexcept
+unordered_map string_view
 unordered_map tuple
 unordered_map version
+unordered_set cctype
 unordered_set compare
 unordered_set cstdint
+unordered_set cstdio
 unordered_set cstring
+unordered_set cwchar
+unordered_set cwctype
 unordered_set initializer_list
+unordered_set iosfwd
 unordered_set limits
 unordered_set optional
+unordered_set stdexcept 
+unordered_set string_view
 unordered_set tuple
 unordered_set version
 utility compare
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp
index 148a6dbc0d3e4..f5a05ee300dd7 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp
@@ -152,17 +152,11 @@
 #    error "__cpp_lib_optional should have the value 202110L in c++26"
 #  endif
 
-#  if !defined(_LIBCPP_VERSION)
-#    ifndef __cpp_lib_optional_range_support
-#      error "__cpp_lib_optional_range_support should be defined in c++26"
-#    endif
-#    if __cpp_lib_optional_range_support != 202406L
-#      error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
-#    endif
-#  else
-#    ifdef __cpp_lib_optional_range_support
-#      error "__cpp_lib_optional_range_support should not be defined because it is unimplemented in libc++!"
-#    endif
+#  ifndef __cpp_lib_optional_range_support
+#    error "__cpp_lib_optional_range_support should be defined in c++26"
+#  endif
+#  if __cpp_lib_optional_range_support != 202406L
+#    error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
 #  endif
 
 #endif // TEST_STD_VER > 23
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 962688e06188a..ba445248a711f 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -7443,17 +7443,11 @@
 #    error "__cpp_lib_optional should have the value 202110L in c++26"
 #  endif
 
-#  if !defined(_LIBCPP_VERSION)
-#    ifndef __cpp_lib_optional_range_support
-#      error "__cpp_lib_optional_range_support should be defined in c++26"
-#    endif
-#    if __cpp_lib_optional_range_support != 202406L
-#      error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
-#    endif
-#  else
-#    ifdef __cpp_lib_optional_range_support
-#      error "__cpp_lib_optional_range_support should not be defined because it is unimplemented in libc++!"
-#    endif
+#  ifndef __cpp_lib_optional_range_support
+#    error "__cpp_lib_optional_range_support should be defined in c++26"
+#  endif
+#  if __cpp_lib_optional_range_support != 202406L
+#    error "__cpp_lib_optional_range_support should have the value 202406L in c++26"
 #  endif
 
 #  ifndef __cpp_lib_out_ptr
diff --git a/libcxx/test/std/utilities/optional/iterator.pass.cpp b/libcxx/test/std/utilities/optional/iterator.pass.cpp
new file mode 100644
index 0000000000000..d9add15128ece
--- /dev/null
+++ b/libcxx/test/std/utilities/optional/iterator.pass.cpp
@@ -0,0 +1,124 @@
+//===----------------------------------------------------------------------===//
+//
+// 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;
+
+// 1: optional::iterator and optional const_iterator satisfy contiguous_iterator and random_access_iterator
+// 2. The value types and reference types for optional::iterator and optional::const_iterator are {_Tp, _Tp&} and {_Tp, const _Tp&} respectively
+// 3: The optional::begin() and optional::end() are marked noexcept.
+// 4: optionals that have a value have begin() != end(), whereas one that doesn't has begin() == end();
+// 5: The corresponding size for the following optionals is respected: has_value() == 1, !has_value() == 0
+// 6: Dereferencing an engaged optional's iterator returns the correct value.
+// 7: std::ranges::enable_view<optional<T>> == true, and std::format_kind<optional<T>> == true
+// 8: Verify that an iterator for loop counts only 1 item for an engaged optional, and 0 for an unegaged one.
+// 9: An optional with value that is reset will have a begin() == end(), then when it is reassigned a value, begin() != end(), and *begin() will contain the new value.
+
+#include <cassert>
+#include <optional>
+
+#include "test_macros.h"
+#include "check_assertion.h"
+
+constexpr int test_loop(const std::optional<char> val) {
+  int size = 0;
+  for (auto&& v : val) {
+    std::ignore = v;
+    size++;
+  }
+  return size;
+}
+
+constexpr bool test_reset() {
+  std::optional<int> val{1};
+  assert(val.begin() != val.end());
+  val.reset();
+  assert(val.begin() == val.end());
+  val = 1;
+  assert(val.begin() != val.end());
+  assert(*(val.begin()) == 1);
+  return true;
+}
+
+int main(int, char**) {
+  constexpr const std::optional<char> opt{'a'};
+  constexpr std::optional<char> unengaged_opt;
+  std::optional<char> nonconst_opt{'n'};
+
+  // 1
+  {
+    static_assert(std::contiguous_iterator<decltype(nonconst_opt.begin())>);
+    static_assert(std::contiguous_iterator<decltype(nonconst_opt.end())>);
+    static_assert(std::random_access_iterator<decltype(nonconst_opt.begin())>);
+    static_assert(std::random_access_iterator<decltype(nonconst_opt.end())>);
+
+    static_assert(std::contiguous_iterator<decltype(opt.begin())>);
+    static_assert(std::contiguous_iterator<decltype(opt.end())>);
+    static_assert(std::random_access_iterator<decltype(opt.begin())>);
+    static_assert(std::random_access_iterator<decltype(opt.end())>);
+  }
+
+  { // 2
+    static_assert(std::same_as<typename decltype(opt.begin())::value_type, char>);
+    static_assert(std::same_as<typename decltype(opt.begin())::reference, const char&>);
+    static_assert(std::same_as<typename decltype(nonconst_opt.begin())::value_type, char>);
+    static_assert(std::same_as<typename decltype(nonconst_opt.begin())::reference, char&>);
+  }
+
+  // 3
+  {
+    ASSERT_NOEXCEPT(opt.begin());
+    ASSERT_NOEXCEPT(opt.end());
+    ASSERT_NOEXCEPT(nonconst_opt.begin());
+    ASSERT_NOEXCEPT(nonconst_opt.end());
+  }
+
+  { // 4
+    static_assert(opt.begin() != opt.end());
+    static_assert(unengaged_opt.begin() == unengaged_opt.end());
+    assert(unengaged_opt.begin() == unengaged_opt.end());
+    assert(opt.begin() != opt.end());
+    assert(nonconst_opt.begin() != opt.end());
+  }
+
+  // 5
+  {
+    static_assert(std::ranges::size(opt) == 1);
+    static_assert(std::ranges::size(unengaged_opt) == 0);
+    assert(std::ranges::size(opt) == 1);
+    assert(std::ranges::size(unengaged_opt) == 0);
+  }
+
+  { // 6
+    static_assert(*opt.begin() == 'a');
+    assert(*(opt.begin()) == 'a');
+    assert(*(nonconst_opt.begin()) == 'n');
+  }
+
+  { // 7
+    static_assert(std::ranges::enable_view<std::optional<char>> == true);
+    assert(std::format_kind<std::optional<char>> == std::range_format::disabled);
+  }
+
+  { // 8
+    static_assert(test_loop(opt) == 1);
+    static_assert(test_loop(unengaged_opt) == 0);
+    assert(test_loop(opt) == 1);
+    assert(test_loop(unengaged_opt) == 0);
+  }
+
+  { // 9
+    static_assert(test_reset());
+    assert(test_reset());
+  }
+
+  return 0;
+}
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index fe175fd758726..152adfbbc52da 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1014,7 +1014,6 @@ def add_version_header(tc):
             "name": "__cpp_lib_optional_range_support",
             "values": {"c++26": 202406},  # P3168R2 Give std::optional ...
[truncated]

@Zingam
Copy link
Contributor

Zingam commented Jul 18, 2025

Thanks for working on this. A nice pre-requisit to optional<T&>. If you decide to work on it, please let me know. I have it assigned to me but I've done minimal work yet.

Copy link

github-actions bot commented Jul 18, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

…r inclusion in __pstl/default.h in C++17 and C++20

The circular include looks like the following:
optional -> __format/range_format.h -> __format/concepts.h -> __format/format_parse_context.h -> string_view -> algorithm -> __algorithm/pstl.h -> __pstl/backend.h -> __pstl/backends/default.h -> optional
…2, also remove check_assertion.h header in test due to it breaking
Comment on lines +120 to +121
template <class _Tp>
friend class optional;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should guard this declaration with _LIBCPP_STD_VER >= 26 (or at least _LIBCPP_STD_VER >= 17). Although the declaration of span wasn't guarded previously.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I'm neutral on this. I don't think it hurts to have the friend declaration in all standard modes, but it also doesn't hurt to make it more strict and guard it. I do have some questions related to using __wrap_iter at all though, see below.

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the patch! This seems to check most of the boxes for what we're usually looking in a patch, which is great. I do have some comments and a few requests for changes.

Comment on lines +120 to +121
template <class _Tp>
friend class optional;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I'm neutral on this. I don't think it hurts to have the friend declaration in all standard modes, but it also doesn't hurt to make it more strict and guard it. I do have some questions related to using __wrap_iter at all though, see below.

public:
using value_type = _Tp;
# if _LIBCPP_STD_VER >= 26
using iterator = __wrap_iter<pointer>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if __wrap_iter is the right tool for the job here. __wrap_iter is basically __random_access_iterator except it has a weird name due to historical reasons. Recently, we've been trying to maintain relevant static information in the type of the iterators, see for example this discussion or this one yielding this issue.

As you have it right now, std::optional iterators would be interchangeable with std::vector iterators. I'm not certain that's what we want since that could lead to unintended misuse from users, and we're also dropping a lot of information on the floor (namely the fact that the range's size is at most 1).

My current thinking is that we should either:

  • Define an iterator that is specific to std::optional, or
  • Define an iterator type that encodes the static information about the maximum size of the range being N. Something like __statically_bounded_iterator<Underlying, N>, although that name clashes with libcxx/include/__iterator/static_bounded_iter.h which is used for hardening. The idea is similar, except the iterator used by optional doesn't need to physically maintain its bounds, it just wants to encode that static knowledge somewhere.

This actually makes me realize that there's something else we should probably do as well: we should probably provide an option to use hardened __static_bounded_iters in optional (behind an ABI macro).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally think the second option sounds good, but should go in a different PR of course. It might also be useful for other containers like inplace_vector.

As for the __static_bounded_iter suggestion, I originally implemented hardening with __bounded_iter (but kept it out of the PR to get some input first), which felt like the better choice since we can't know if the optional is going to have a range of 0 or 1 at compile time(?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ldionne What improvement do you see by providing an optional-specific iterator? I don't really see how such an iterator would buy us anything. It would be an improvement for bounds-safety, but that's not provided here anyways.

return const_iterator(std::addressof(this->__get()));
}

_LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept { return begin() + this->has_value(); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept { return begin() + this->has_value(); }
_LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept { return begin() + (this->has_value() ? 1 : 0); }

Both are equivalent, but using a bool as an integer is usually frowned upon. This is a suggestion, not a hard request.

Copy link
Contributor Author

@smallp-o-p smallp-o-p Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I would've done that if not for the wording in the proposal, and I'm not entirely sure how much libc++ prioritizes following the standard to the absolute letter:

Returns: begin() + has_value()

Implicit conversions are evil though, so it's probably better to have it be explicit out of principle.

@@ -2,6 +2,7 @@ algorithm cctype
algorithm climits
algorithm compare
algorithm cstdint
algorithm cstdio
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do we get that from?

Copy link
Contributor Author

@smallp-o-p smallp-o-p Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure it comes from a transitive include originating in __format/range_format.h. I ran into an issue when including it unguarded because somewhere along the chain string_view gets included twice in an <algorithm> sub-header but only in C++17 and C++20--where the issues cropped up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to request that we split this into three files:

  • One test file for std::optional::begin() and std::optional::begin() const.
  • One test file for std::optional::end() and std::optional::end() const.
  • One test file for the std::optional::iterator and std::optional::const_iterator types.

I think that is what gets us closest to our usual "one test file per public API function" without getting into diminishing returns.

assert(test<int>());
assert(test<char>());
assert(test<const int>());
assert(test<const char>());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I like how you organized the tests now. FYI only - I am not asking for any changes: there is an alternative to iterate over multiple types a helper types::for_each(types::integer_types(), IntegerTypesTest{}); an example usage is here: https://github.com/llvm/llvm-project/blob/9ae7490c6251ec60ab978bb818e89ca586e0c9c9/libcxx/test/std/ranges/range.factories/range.iota.view/indices.pass.cpp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

P3168R2: Give std::optional Range Support
7 participants