Skip to content

Conversation

smallp-o-p
Copy link
Contributor

@smallp-o-p smallp-o-p commented Aug 25, 2025

Resolves #148131

  • Unlock std::optional<T&> implementation
  • Allow instantiations of optional<T(&)(...)> and optional<T(&)[]> but disables value_or() and optional::iterator + all iterator related functions
  • Update documentation
  • Update tests

@smallp-o-p smallp-o-p requested a review from a team as a code owner August 25, 2025 04:23
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Aug 25, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 25, 2025

@llvm/pr-subscribers-libcxx

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

Changes

Resolves #148131

  • Unlock std::optional&lt;T&amp;&gt; implementation, implementation allows optional&lt;T(&amp;)(...)&gt; and optional&lt;T(&amp;)[]&gt; to be instantiated but disables value_or() and optional::iterator (and all iterator related functions)
  • Update documentation
  • Update tests

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

29 Files Affected:

  • (modified) libcxx/docs/FeatureTestMacroTable.rst (+2)
  • (modified) libcxx/docs/ReleaseNotes/22.rst (+1)
  • (modified) libcxx/docs/Status/Cxx2cPapers.csv (+1-1)
  • (modified) libcxx/include/__iterator/wrap_iter.h (+2-2)
  • (modified) libcxx/include/optional (+83-47)
  • (modified) libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp (+4-5)
  • (added) libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.compile.pass.cpp (+27)
  • (modified) libcxx/test/std/utilities/optional/optional.iterator/begin.pass.cpp (+6-1)
  • (modified) libcxx/test/std/utilities/optional/optional.iterator/end.pass.cpp (+7-1)
  • (modified) libcxx/test/std/utilities/optional/optional.iterator/iterator.pass.cpp (+17-10)
  • (modified) libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp (+80)
  • (modified) libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp (+26)
  • (modified) libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp (+115-13)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.assign/assign_value.pass.cpp (+35-1)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.assign/emplace.pass.cpp (+24-1)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ctor.verify.cpp (+4)
  • (added) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ref_t.pass.cpp (+40)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.dtor/dtor.pass.cpp (+8-2)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.mod/reset.pass.cpp (+11)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference.pass.cpp (+13-1)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const.pass.cpp (+19)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/has_value.pass.cpp (+8-1)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow.pass.cpp (+25)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow_const.pass.cpp (+19)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value.pass.cpp (+8)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp (+8)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp (+7-1)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional_requires_destructible_object.verify.cpp (+10-3)
  • (modified) libcxx/utils/generate_feature_test_macro_components.py (+1)
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 358889d8dbc37..c10fc4c365407 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -480,6 +480,8 @@ Status
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_not_fn``                                       ``202306L``
     ---------------------------------------------------------- -----------------
+    ``__cpp_lib_optional``                                     ``202506L``
+    ---------------------------------------------------------- -----------------
     ``__cpp_lib_optional_range_support``                       ``202406L``
     ---------------------------------------------------------- -----------------
     ``__cpp_lib_out_ptr``                                      ``202311L``
diff --git a/libcxx/docs/ReleaseNotes/22.rst b/libcxx/docs/ReleaseNotes/22.rst
index a26c5476d421b..e070169c87ba3 100644
--- a/libcxx/docs/ReleaseNotes/22.rst
+++ b/libcxx/docs/ReleaseNotes/22.rst
@@ -39,6 +39,7 @@ Implemented Papers
 ------------------
 
 - P2321R2: ``zip`` (`Github <https://github.com/llvm/llvm-project/issues/105169>`__) (The paper is partially implemented. ``zip_transform_view`` is implemented in this release)
+- P2988R12: ``std::optional<T&>`` (`Github <https://github.com/llvm/llvm-project/issues/148131>`__)
 - 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 3b8b2b7ad0b3f..5e28875c26944 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -122,7 +122,7 @@
 "`P3293R3 <https://wg21.link/P3293R3>`__","Splicing a base class subobject","2025-06 (Sofia)","","",""
 "`P3491R3 <https://wg21.link/P3491R3>`__","``define_static_{string,object,array}``","2025-06 (Sofia)","","",""
 "`P3096R12 <https://wg21.link/P3096R12>`__","Function Parameter Reflection in Reflection for C++26","2025-06 (Sofia)","","",""
-"`P2988R12 <https://wg21.link/P2988R12>`__","``std::optional<T&>``","2025-06 (Sofia)","","",""
+"`P2988R12 <https://wg21.link/P2988R12>`__","``std::optional<T&>``","2025-06 (Sofia)","|Complete|","22",""
 "`P3348R4 <https://wg21.link/P3348R4>`__","C++26 should refer to C23 not C17","2025-06 (Sofia)","","",""
 "`P3037R6 <https://wg21.link/P3037R6>`__","``constexpr`` ``std::shared_ptr`` and friends","2025-06 (Sofia)","","",""
 "`P3284R4 <https://wg21.link/P3284R4>`__","``write_env`` and ``unstoppable`` Sender Adaptors","2025-06 (Sofia)","","",""
diff --git a/libcxx/include/__iterator/wrap_iter.h b/libcxx/include/__iterator/wrap_iter.h
index 7610586ddecbb..8e5c7388812d3 100644
--- a/libcxx/include/__iterator/wrap_iter.h
+++ b/libcxx/include/__iterator/wrap_iter.h
@@ -117,8 +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 _Tp, class>
+  friend struct __optional_iterator;
 };
 
 template <class _Iter1>
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 39fcaa2c2ec18..489691f33e0cd 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -161,8 +161,8 @@ namespace std {
     constexpr T &value() &;
     constexpr T &&value() &&;
     constexpr const T &&value() const &&;
-    template<class U> constexpr T value_or(U &&) const &;
-    template<class U> constexpr T value_or(U &&) &&;
+    template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) const &;
+    template<class U = remove_cv_t<T>> constexpr T value_or(U&& v) &&;
 
     // [optional.monadic], monadic operations
     template<class F> constexpr auto and_then(F&& f) &;         // since C++23
@@ -412,9 +412,6 @@ struct __optional_storage_base : __optional_destruct_base<_Tp> {
   }
 };
 
-// optional<T&> is currently required to be ill-formed. However, it may
-// be allowed in the future. For this reason, it has already been implemented
-// to ensure we can make the change in an ABI-compatible manner.
 template <class _Tp>
 struct __optional_storage_base<_Tp, true> {
   using value_type                 = _Tp;
@@ -607,19 +604,19 @@ struct __is_std_optional : false_type {};
 template <class _Tp>
 struct __is_std_optional<optional<_Tp>> : true_type {};
 
-template <class _Tp>
-class _LIBCPP_DECLSPEC_EMPTY_BASES optional
-    : private __optional_move_assign_base<_Tp>,
-      private __optional_sfinae_ctor_base_t<_Tp>,
-      private __optional_sfinae_assign_base_t<_Tp> {
-  using __base _LIBCPP_NODEBUG = __optional_move_assign_base<_Tp>;
+template <class _Tp, class = void>
+struct __optional_iterator {};
 
-  using __pointer _LIBCPP_NODEBUG       = std::add_pointer_t<_Tp>;
-  using __const_pointer _LIBCPP_NODEBUG = std::add_pointer_t<const _Tp>;
+template <class _Tp>
+struct __optional_iterator<
+    _Tp,
+    enable_if_t<!(is_lvalue_reference_v<_Tp> && is_function_v<__libcpp_remove_reference_t<_Tp>>) &&
+                !(is_lvalue_reference_v<_Tp> && is_array_v<__libcpp_remove_reference_t<_Tp>>)> > {
+private:
+  using __pointer _LIBCPP_NODEBUG       = std::add_pointer_t<remove_reference_t<_Tp>>;
+  using __const_pointer _LIBCPP_NODEBUG = std::add_pointer_t<const remove_reference_t<_Tp>>;
 
 public:
-  using value_type = _Tp;
-
 #    if _LIBCPP_STD_VER >= 26
 #      ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
   using iterator       = __bounded_iter<__wrap_iter<__pointer>>;
@@ -628,18 +625,67 @@ public:
   using iterator       = __wrap_iter<__pointer>;
   using const_iterator = __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);
+#      ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
+    return std::__make_bounded_iter(
+        std::__wrap_iter<__pointer>(std::addressof(__derived_self.__get())),
+        std::__wrap_iter<__pointer>(std::addressof(__derived_self.__get())),
+        std::__wrap_iter<__pointer>(std::addressof(__derived_self.__get()) + (__derived_self.has_value() ? 1 : 0)));
+#      else
+    return iterator(std::addressof(__derived_self.__get()));
+#      endif
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator begin() const noexcept {
+    auto& __derived_self = static_cast<const optional<_Tp>&>(*this);
+#      ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
+    return std::__make_bounded_iter(
+        std::__wrap_iter<__const_pointer>(std::addressof(__derived_self.__get())),
+        std::__wrap_iter<__const_pointer>(std::addressof(__derived_self.__get())),
+        std::__wrap_iter<__const_pointer>(
+            std::addressof(__derived_self.__get()) + (__derived_self.has_value() ? 1 : 0)));
+#      else
+    return const_iterator(std::addressof(__derived_self.__get()));
+#      endif
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept {
+    return begin() + (static_cast<optional<_Tp>&>(*this).has_value() ? 1 : 0);
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator end() const noexcept {
+    return begin() + (static_cast<const optional<_Tp>&>(*this).has_value() ? 1 : 0);
+  }
 #    endif
+};
+
+template <class _Tp>
+class _LIBCPP_DECLSPEC_EMPTY_BASES optional
+    : private __optional_move_assign_base<_Tp>,
+      private __optional_sfinae_ctor_base_t<_Tp>,
+      private __optional_sfinae_assign_base_t<_Tp>,
+      public __optional_iterator<_Tp> {
+  using __base _LIBCPP_NODEBUG = __optional_move_assign_base<_Tp>;
+
+public:
+  using value_type = _Tp;
+
   using __trivially_relocatable _LIBCPP_NODEBUG =
       conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, optional, void>;
   using __replaceable _LIBCPP_NODEBUG = conditional_t<__is_replaceable_v<_Tp>, optional, void>;
 
 private:
-  // Disable the reference extension using this static assert.
   static_assert(!is_same_v<__remove_cvref_t<value_type>, in_place_t>,
                 "instantiation of optional with in_place_t is ill-formed");
   static_assert(!is_same_v<__remove_cvref_t<value_type>, nullopt_t>,
                 "instantiation of optional with nullopt_t is ill-formed");
-  static_assert(!is_reference_v<value_type>, "instantiation of optional with a reference type is ill-formed");
+#    if _LIBCPP_STD_VER >= 26
+  static_assert(!is_rvalue_reference_v<_Tp>, "instantiation of optional with an rvalue reference type is ill-formed");
+#    else
+  static_assert(!is_reference_v<_Tp>, "instantiation of optional with a reference type is ill-formed");
+#    endif
   static_assert(is_destructible_v<value_type>, "instantiation of optional with a non-destructible type is ill-formed");
   static_assert(!is_array_v<value_type>, "instantiation of optional with an array type is ill-formed");
 
@@ -832,34 +878,6 @@ public:
     }
   }
 
-#    if _LIBCPP_STD_VER >= 26
-  // [optional.iterators], iterator support
-  _LIBCPP_HIDE_FROM_ABI constexpr iterator begin() noexcept {
-#      ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
-    return std::__make_bounded_iter(
-        std::__wrap_iter<__pointer>(std::addressof(this->__get())),
-        std::__wrap_iter<__pointer>(std::addressof(this->__get())),
-        std::__wrap_iter<__pointer>(std::addressof(this->__get()) + (this->has_value() ? 1 : 0)));
-#      else
-    return iterator(std::addressof(this->__get()));
-#      endif
-  }
-
-  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator begin() const noexcept {
-#      ifdef _LIBCPP_ABI_BOUNDED_ITERATORS_IN_OPTIONAL
-    return std::__make_bounded_iter(
-        std::__wrap_iter<__const_pointer>(std::addressof(this->__get())),
-        std::__wrap_iter<__const_pointer>(std::addressof(this->__get())),
-        std::__wrap_iter<__const_pointer>(std::addressof(this->__get()) + (this->has_value() ? 1 : 0)));
-#      else
-    return const_iterator(std::addressof(this->__get()));
-#      endif
-  }
-
-  _LIBCPP_HIDE_FROM_ABI constexpr iterator end() noexcept { return begin() + (this->has_value() ? 1 : 0); }
-  _LIBCPP_HIDE_FROM_ABI constexpr const_iterator end() const noexcept { return begin() + (this->has_value() ? 1 : 0); }
-#    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());
@@ -919,20 +937,38 @@ public:
     return std::move(this->__get());
   }
 
-  template <class _Up>
+  template <class _Up = remove_cv_t<_Tp>>
+#    if _LIBCPP_STD_VER >= 26
+    requires(!(is_lvalue_reference_v<_Tp> && is_function_v<__libcpp_remove_reference_t<_Tp>>) &&
+             !(is_lvalue_reference_v<_Tp> && is_array_v<__libcpp_remove_reference_t<_Tp>>))
+#    endif
   _LIBCPP_HIDE_FROM_ABI constexpr value_type value_or(_Up&& __v) const& {
     static_assert(is_copy_constructible_v<value_type>, "optional<T>::value_or: T must be copy constructible");
     static_assert(is_convertible_v<_Up, value_type>, "optional<T>::value_or: U must be convertible to T");
     return this->has_value() ? this->__get() : static_cast<value_type>(std::forward<_Up>(__v));
   }
 
-  template <class _Up>
+  template <class _Up = remove_cv_t<_Tp>>
+#    if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#    endif
   _LIBCPP_HIDE_FROM_ABI constexpr value_type value_or(_Up&& __v) && {
     static_assert(is_move_constructible_v<value_type>, "optional<T>::value_or: T must be move constructible");
     static_assert(is_convertible_v<_Up, value_type>, "optional<T>::value_or: U must be convertible to T");
     return this->has_value() ? std::move(this->__get()) : static_cast<value_type>(std::forward<_Up>(__v));
   }
 
+#    if _LIBCPP_STD_VER >= 26
+  template <class _Up = remove_cv_t<_Tp>>
+    requires(is_lvalue_reference_v<_Tp> &&
+             !(is_function_v<__libcpp_remove_reference_t<_Tp>> || is_array_v<__libcpp_remove_reference_t<_Tp>>))
+  _LIBCPP_HIDE_FROM_ABI constexpr value_type value_or(_Up&& __v) && {
+    static_assert(is_move_constructible_v<value_type>, "optional<T>::value_or: T must be move constructible");
+    static_assert(is_convertible_v<_Up, value_type>, "optional<T>::value_or: U must be convertible to T");
+    return this->has_value() ? this->__get() : static_cast<value_type>(std::forward<_Up>(__v));
+  }
+#    endif
+
 #    if _LIBCPP_STD_VER >= 23
   template <class _Func>
   _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) & {
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 3cdd7553e2e5d..472049a91a8d1 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
@@ -23,8 +23,7 @@ concept has_iterator_aliases = requires {
 
 static_assert(has_iterator_aliases<std::optional<int>>);
 static_assert(has_iterator_aliases<std::optional<const int>>);
-
-// TODO: Uncomment these once P2988R12 is implemented, as they would be testing optional<T&>
-
-// static_assert(!has_iterator_aliases<std::optional<int (&)[]>>);
-// static_assert(!has_iterator_aliases<std::optional<void (&)(int, char)>>);
+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(&)()>>);
diff --git a/libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.compile.pass.cpp b/libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.compile.pass.cpp
new file mode 100644
index 0000000000000..298e2f6f55d4d
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.compile.pass.cpp
@@ -0,0 +1,27 @@
+
+//===----------------------------------------------------------------------===//
+//
+// 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 U> T optional<T>::value_or(U&&);
+
+#include <optional>
+
+template <typename Opt, typename T>
+concept has_value_or = requires(Opt opt, T&& t) {
+  { opt.value_or(t) } -> std::same_as<T>;
+};
+
+static_assert(has_value_or<std::optional<int>, int>);
+static_assert(has_value_or<std::optional<int&>, int&>);
+static_assert(has_value_or<std::optional<const int&>, const int&>);
+static_assert(!has_value_or<std::optional<int(&)[1]>&&, int(&)[1]>);
+static_assert(!has_value_or<std::optional<int(&)()>&&, int(&)()>);
diff --git a/libcxx/test/std/utilities/optional/optional.iterator/begin.pass.cpp b/libcxx/test/std/utilities/optional/optional.iterator/begin.pass.cpp
index df95a8df3793f..81234525923a1 100644
--- a/libcxx/test/std/utilities/optional/optional.iterator/begin.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.iterator/begin.pass.cpp
@@ -21,7 +21,8 @@
 
 template <typename T>
 constexpr bool test() {
-  std::optional<T> opt{T{}};
+  std::remove_reference_t<T> t = std::remove_reference_t<T>{};
+  std::optional<T> opt{t};
 
   { // begin() is marked noexcept
     static_assert(noexcept(opt.begin()));
@@ -53,6 +54,10 @@ constexpr bool tests() {
   assert(test<char>());
   assert(test<const int>());
   assert(test<const char>());
+  assert(test<int&>());
+  assert(test<char&>());
+  assert(test<const int&>());
+  assert(test<const char&>());
   return true;
 }
 
diff --git a/libcxx/test/std/utilities/optional/optional.iterator/end.pass.cpp b/libcxx/test/std/utilities/optional/optional.iterator/end.pass.cpp
index 966c3e7441880..c62c9fc7746d6 100644
--- a/libcxx/test/std/utilities/optional/optional.iterator/end.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.iterator/end.pass.cpp
@@ -17,6 +17,7 @@
 #include <iterator>
 #include <optional>
 #include <ranges>
+#include <type_traits>
 #include <utility>
 
 template <typename T>
@@ -41,7 +42,8 @@ constexpr bool test() {
     assert(it2 == std::as_const(disengaged).end());
   }
 
-  std::optional<T> engaged{T{}};
+  std::remove_reference_t<T> t = std::remove_reference_t<T>{};
+  std::optional<T> engaged{t};
 
   { // end() != begin() if the optional is engaged
     auto it  = engaged.end();
@@ -62,6 +64,10 @@ constexpr bool tests() {
   assert(test<char>());
   assert(test<const int>());
   assert(test<const char>());
+  assert(test<int&>());
+  assert(test<char&>());
+  assert(test<const int&>());
+  assert(test<const char&>());
 
   return true;
 }
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 1203290a0290a..34d0d6b135564 100644
--- a/libcxx/test/std/utilities/optional/optional.iterator/iterator.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.iterator/iterator.pass.cpp
@@ -20,9 +20,10 @@
 #include <type_traits>
 #include <utility>
 
-template <typename T, T __val>
+template <typename T, std::remove_reference_t<T> __val>
 constexpr bool test() {
-  std::optional<T> opt{__val};
+  std::remove_reference_t<T> v{__val};
+  std::optional<T> opt{v};
 
   { // Dereferencing an iterator of an engaged optional will return the same value that the optional holds.
     auto it  = opt.begin();
@@ -41,13 +42,14 @@ constexpr bool test() {
     assert(std::random_access_iterator<decltype(it2)>);
   }
 
-  { // const_iterator::value_type == std::remove_cv_t<T>, const_iterator::reference == const T&, iterator::value_type = std::remove_cv_t<T>, iterator::reference == T&
+  { // const_iterator::value_type == std::remove_cvref_t<T>, const_iterator::reference == const T&, iterator::value_type = std::remove_cvref_t<T>, iterator::reference == T&
+    // std::remove_cv_t is impossible for optional<T&>
     auto it  = opt.begin();
     auto it2 = std::as_const(opt).begin();
-    assert((std::is_same_v<typename decltype(it)::value_type, std::remove_cv_t<T>>));
-    assert((std::is_same_v<typename decltype(it)::reference, T&>));
-    assert((std::is_same_v<typename decltype(it2)::value_type, std::remove_cv_t<T>>));
-    assert((std::is_same_v<typename decltype(it2)::reference, const T&>));
+    assert((std::is_same_v<typename decltype(it)::value_type, std::remove_cvref_t<T>>));
+    assert((std::is_same_v<typename decltype(it)::reference, std::remove_reference_t<T>&>));
+    assert((std::is_same_v<typename decltype(it2)::value_type, std::remove_cvref_t<T>>));
+    assert((std::is_same_v<typename decltype(it2)::reference, const std::remove_reference_t<T>&>));
   }
 
   { // std::ranges::size for an engaged optional<T> == 1, disengaged optional<T> == 0
@@ -68,13 +70,13 @@ constexpr bool test() {
   // 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.
   {
-    std::optional<T> val{__val};
+    std::optional<T> val{v};
     assert(val.begin() != val.end());
     val.reset();
     assert(val.begin() == val.end());
-    val.emplace(__val);
+    val.emplace(v);
     assert(val.begin() != val.end());
-    assert(*(val.begin()) == __val);
+    assert(*(val.begin()) == v);
   }
 
   return true;
@@ -86,6 +88,11 @@ constexpr bool tests() {
   assert((test<bool, true>()));
   assert((test<const int, 2>()));
   assert((test<const char, 'b'>()));
+  assert((test<int&, 1>()));
+  assert((test<char&, 'a'>()));
+  assert((test<bool&, true>()));
+  assert((test<const int&, 2>()));
+  assert((test<const char&, 'b'>()));
 
   return true;
 }
diff --git a/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp b/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
index 97305d976e066..3d945e437151e 100644
--- a/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
@@ -257,8 +257,88 @@ c...
[truncated]

Copy link

github-actions bot commented Aug 25, 2025

✅ With the latest revision this PR passed the Python code formatter.

Copy link

github-actions bot commented Aug 25, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff origin/main HEAD --extensions ,h,cpp -- libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.compile.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ref_constructs_from_temporary.verify.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ref_t.pass.cpp libcxx/include/__iterator/wrap_iter.h libcxx/include/optional libcxx/include/version libcxx/test/libcxx/utilities/optional/optional.iterator/iterator.compile.pass.cpp libcxx/test/std/language.support/support.limits/support.limits.general/optional.version.compile.pass.cpp libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp libcxx/test/std/utilities/optional/optional.iterator/begin.pass.cpp libcxx/test/std/utilities/optional/optional.iterator/end.pass.cpp libcxx/test/std/utilities/optional/optional.iterator/iterator.pass.cpp libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.assign/assign_value.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.assign/emplace.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ctor.verify.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/move.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.dtor/dtor.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.mod/reset.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.observe/has_value.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow_const.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp libcxx/test/std/utilities/optional/optional.object/optional_requires_destructible_object.verify.cpp

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from clang-format here.
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 387240358..3ccec37e2 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -418,7 +418,7 @@ struct __optional_storage_base : __optional_destruct_base<_Tp> {
   }
 
   template <class _Up>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __assign_from_val(_Up&& __val){
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __assign_from_val(_Up&& __val) {
     this->__get() = std::forward<_Up>(__val);
   }
 };
@@ -476,7 +476,7 @@ struct __optional_storage_base<_Tp, true> {
   }
 
   template <class _Up>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __assign_from_val(_Up&& __val){
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __assign_from_val(_Up&& __val) {
     __value_ = std::addressof(__val);
   }
 };
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.assign/assign_value.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.assign/assign_value.pass.cpp
index b65279b3a..ddb9ffc4b 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.assign/assign_value.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.assign/assign_value.pass.cpp
@@ -284,8 +284,8 @@ constexpr void test_with_ref() {
     optional<T&> opt{t};
     opt = t2;
 
-    assert(std::addressof(*opt) != std::addressof(t)); 
-    assert(std::addressof(*opt) == std::addressof(t2)); 
+    assert(std::addressof(*opt) != std::addressof(t));
+    assert(std::addressof(*opt) == std::addressof(t2));
   }
   // test that reassigning the reference for an optional<T&> doesn't affect the objet it's holding a reference to
   {

@smallp-o-p smallp-o-p marked this pull request as draft August 25, 2025 04:43
@smallp-o-p smallp-o-p changed the title [libc++] Implement P2988R12: `std::optional<T&> [libc++] Implement P2988R12: std::optional<T&> Aug 25, 2025
@smallp-o-p smallp-o-p marked this pull request as ready for review September 25, 2025 19:58
@smallp-o-p
Copy link
Contributor Author

Marking as ready + a reminder ping

@vogelsgesang
Copy link
Member

vogelsgesang commented Sep 26, 2025

afaict, the physical layout representation of a an optional<Foo&> would be

struct OptionalFooRef {
  union {
    char __null_state_;
    Foo& __val_;
  };
  bool __engaged_;
};

I think a more optimal representation would be

struct OptionalFooRef {
  // nullopt encoded as nullptr, since `nullptr` is not a valid reference
  Foo* __val_;
};

This would allow me to use optional<Foo&> as a more type-safe replacement for Foo* without any performance penalties.

Given that optional<Foo&> was previously forbidden, this wouldn't be an ABI break. However, if we would merge your PR as is, we could no longer do that optimization later on without breaking the ABI.

(Please note that I am not a contributor / reviewer for libc++. So please wait for feedback from others before acting on my comment. Not sure if I might be sending you into the wrong direction here 🙂)

@smallp-o-p
Copy link
Contributor Author

smallp-o-p commented Sep 26, 2025

afaict, the physical layout representation of a an optional<Foo&> would be

struct OptionalFooRef {

  union {

    char __null_state_;

    Foo& __val_;

  };

  bool __engaged_;

};

I think a more optimal representation would be

struct OptionalFooRef {

  // nullopt encoded as nullptr, since `nullptr` is not a valid reference

  Foo* __val_;

};

This would allow me to use optional<Foo&> as a more type-safe replacement for Foo* without any performance penalties.

Given that optional<Foo&> was previously forbidden, this wouldn't be an ABI break. However, if we would merge your PR as is, we could no longer do that optimization later on without breaking the ABI.

(Please note that I am not a contributor / reviewer for libc++. So please wait for feedback from others before acting on my comment. Not sure if I might be sending you into the wrong direction here 🙂)

The storage base for optional<T&> is implemented as you specify via a template specialization SFINAE, and it's actually been there for quite some time—I want to guess when optional was originally implemented?

Either way you'll be pleased to know the original implementers thought of this already 😄

@frederick-vs-ja
Copy link
Contributor

frederick-vs-ja commented Sep 26, 2025

I think a more optimal representation would be

struct OptionalFooRef {
  // nullopt encoded as nullptr, since `nullptr` is not a valid reference
  Foo* __val_;
};

Such a representation has been already implemented for years.

// optional<T&> is currently required to be ill-formed. However, it may
// be allowed in the future. For this reason, it has already been implemented
// to ensure we can make the change in an ABI-compatible manner.
template <class _Tp>
struct __optional_storage_base<_Tp, true> {
using value_type = _Tp;
using __raw_type _LIBCPP_NODEBUG = remove_reference_t<_Tp>;
__raw_type* __value_;
template <class _Up>
static _LIBCPP_HIDE_FROM_ABI constexpr bool __can_bind_reference() {
using _RawUp = __libcpp_remove_reference_t<_Up>;
using _UpPtr = _RawUp*;
using _RawTp = __libcpp_remove_reference_t<_Tp>;
using _TpPtr = _RawTp*;
using _CheckLValueArg =
integral_constant<bool,
(is_lvalue_reference<_Up>::value && is_convertible<_UpPtr, _TpPtr>::value) ||
is_same<_RawUp, reference_wrapper<_RawTp>>::value ||
is_same<_RawUp, reference_wrapper<__remove_const_t<_RawTp>>>::value >;
return (is_lvalue_reference<_Tp>::value && _CheckLValueArg::value) ||
(is_rvalue_reference<_Tp>::value && !is_lvalue_reference<_Up>::value &&
is_convertible<_UpPtr, _TpPtr>::value);
}
_LIBCPP_HIDE_FROM_ABI constexpr __optional_storage_base() noexcept : __value_(nullptr) {}
template <class _UArg>
_LIBCPP_HIDE_FROM_ABI constexpr explicit __optional_storage_base(in_place_t, _UArg&& __uarg)
: __value_(std::addressof(__uarg)) {
static_assert(__can_bind_reference<_UArg>(),
"Attempted to construct a reference element in tuple from a "
"possible temporary");
}
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void reset() noexcept { __value_ = nullptr; }
_LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return __value_ != nullptr; }
_LIBCPP_HIDE_FROM_ABI constexpr value_type& __get() const& noexcept { return *__value_; }
_LIBCPP_HIDE_FROM_ABI constexpr value_type&& __get() const&& noexcept { return std::forward<value_type>(*__value_); }
template <class _UArg>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __construct(_UArg&& __val) {
_LIBCPP_ASSERT_INTERNAL(!has_value(), "__construct called for engaged __optional_storage");
static_assert(__can_bind_reference<_UArg>(),
"Attempted to construct a reference element in tuple from a "
"possible temporary");
__value_ = std::addressof(__val);
}
template <class _That>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __construct_from(_That&& __opt) {
if (__opt.has_value())
__construct(std::forward<_That>(__opt).__get());
}
template <class _That>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __assign_from(_That&& __opt) {
if (has_value() == __opt.has_value()) {
if (has_value())
*__value_ = std::forward<_That>(__opt).__get();
} else {
if (has_value())
reset();
else
__construct(std::forward<_That>(__opt).__get());
}
}
};

@smallp-o-p However, I'm not sure whether __can_bind_reference is still useful (or not harmful). Perhaps we should either remove or update it (with reference_constructs_from_temporary_v).

@smallp-o-p
Copy link
Contributor Author

Wonder what's up with the FreeBSD bot, seems like it can't find reference_constructs_from_temporary_v

@Zingam
Copy link
Contributor

Zingam commented Oct 1, 2025

Wonder what's up with the FreeBSD bot, seems like it can't find reference_constructs_from_temporary_v

It's on Clang 19. It should be updated fairly soon.

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.

P2988R12: std::optional<T&>
6 participants