Skip to content

[libc++] Correct optional<T&> implementation#174537

Merged
frederick-vs-ja merged 29 commits into
llvm:mainfrom
smallp-o-p:optional-ref-fixes
Jan 9, 2026
Merged

[libc++] Correct optional<T&> implementation#174537
frederick-vs-ja merged 29 commits into
llvm:mainfrom
smallp-o-p:optional-ref-fixes

Conversation

@smallp-o-p

@smallp-o-p smallp-o-p commented Jan 6, 2026

Copy link
Copy Markdown
Contributor

Resolves #174350

  • Several issues were found in the current implementation of optional<T&>
    • value(), operator*(), and_then(), transform(), operator->() still provided their ref-qualified versions for rvalues and const&.
      • Using the listed methods on an rvalue optional<T&> would cause a compile failure due to a mismatch in return types.
      • On the latter, operator*, operator-> would return const for a optional<T&>, which is an incorrect deep const.
    • A few constructors were missing (optional<U>&), and most constructors relevant to optional<T&> were missing noexcept
    • Constructors and emplace were not correctly constructing a T& as specified in convert-ref-init-val
  • Also corrects the behavior of value_or which should return remove_cv_t<T> (in our case decay_t<_Tp>)
  • Add several test cases to verify behavior, update value_or tests

@smallp-o-p smallp-o-p requested a review from a team as a code owner January 6, 2026 06:50
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jan 6, 2026
@smallp-o-p smallp-o-p marked this pull request as draft January 6, 2026 06:50
@llvmbot

llvmbot commented Jan 6, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-libcxx

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

Changes

Resolves #174350

  • Several issues were found in the current implementation of optional&lt;T&amp;&gt;
  • value(), operator*(), and_then(), transform(), operator-&gt;() still provided their ref-qualified versions for rvalues and const&amp;.
  • Using the listed methods on an rvalue optional&lt;T&amp;&gt; would cause a compile failure due to a mismatch in return types.
  • On the latter, operator*, operator-&gt; would return const for a optional&lt;T&amp;&gt;, which is an incorrect deep const.
  • Also corrects the behavior of value_or which should return remove_cv_t&lt;T&gt; (in our case decay_t&lt;_Tp&gt;)
  • Add several test cases to verify behavior, update value_or tests

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

28 Files Affected:

  • (modified) libcxx/include/optional (+230-47)
  • (modified) libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.compile.pass.cpp (+2-2)
  • (modified) libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp (+15-12)
  • (modified) libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp (+17)
  • (modified) libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp (+8-6)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.assign/assign_value.pass.cpp (+21-5)
  • (added) libcxx/test/std/utilities/optional/optional.object/optional.object.assign/emplace.compile.fail.cpp (+26)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.assign/emplace.pass.cpp (+1)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.assign/nullopt_t.pass.cpp (+11)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/U.pass.cpp (+40-1)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/const_optional_U.pass.cpp (+48)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/in_place_t.pass.cpp (+19-2)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/move.pass.cpp (+20-12)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/optional_U.pass.cpp (+52)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ref_constructs_from_temporary.verify.cpp (+22-13)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.mod/reset.pass.cpp (+1)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/bool.pass.cpp (+32-2)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference.pass.cpp (+35-2)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const.pass.cpp (+51-30)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_const_rvalue.pass.cpp (+41-2)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/dereference_rvalue.pass.cpp (+41-2)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/has_value.pass.cpp (+6-2)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow.pass.cpp (+5-2)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/op_arrow_const.pass.cpp (+5-2)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_const.pass.cpp (+41-3)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_const_rvalue.pass.cpp (+41-3)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp (+3-1)
  • (modified) libcxx/test/support/copy_move_types.h (+4-1)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 568c86556d156..487cfa1e7ce6e 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -510,8 +510,6 @@ struct __optional_storage_base<_Tp, true> {
 
   _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");
@@ -909,38 +907,61 @@ public:
       : __base(in_place, __il, std::forward<_Args>(__args)...) {}
 
   template <class _Up = _Tp, enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_implicit<_Up>(), int> = 0>
-  _LIBCPP_HIDE_FROM_ABI constexpr optional(_Up&& __v) : __base(in_place, std::forward<_Up>(__v)) {}
+  _LIBCPP_HIDE_FROM_ABI constexpr optional(_Up&& __v)
+#    if _LIBCPP_STD_VER >= 26
+      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up>)
+#    endif
+      : __base(in_place, std::forward<_Up>(__v)) {
+  }
 
   template <class _Up                                                                        = remove_cv_t<_Tp>,
             enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_explicit<_Up>(), int> = 0>
-  _LIBCPP_HIDE_FROM_ABI constexpr explicit optional(_Up&& __v) : __base(in_place, std::forward<_Up>(__v)) {}
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit optional(_Up&& __v)
+#    if _LIBCPP_STD_VER >= 26
+      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up>)
+#    endif
+      : __base(in_place, std::forward<_Up>(__v)) {
+  }
 
   // LWG2756: conditionally explicit conversion from const optional<_Up>&
   template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>::template __enable_implicit<_Up>(), int> = 0>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 optional(const optional<_Up>& __v) {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 optional(const optional<_Up>& __v)
+#    if _LIBCPP_STD_VER >= 26
+      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up&>)
+#    endif
+  {
     this->__construct_from(__v);
   }
   template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>::template __enable_explicit<_Up>(), int> = 0>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit optional(const optional<_Up>& __v) {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit optional(const optional<_Up>& __v)
+#    if _LIBCPP_STD_VER >= 26
+      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up&>)
+#    endif
+  {
     this->__construct_from(__v);
   }
 
   // LWG2756: conditionally explicit conversion from optional<_Up>&&
   template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>::template __enable_implicit<_Up>(), int> = 0>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 optional(optional<_Up>&& __v) {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 optional(optional<_Up>&& __v)
+#    if _LIBCPP_STD_VER >= 26
+      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up>)
+#    endif
+  {
     this->__construct_from(std::move(__v));
   }
   template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>::template __enable_explicit<_Up>(), int> = 0>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit optional(optional<_Up>&& __v) {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit optional(optional<_Up>&& __v)
+#    if _LIBCPP_STD_VER >= 26
+      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up>)
+#    endif
+  {
     this->__construct_from(std::move(__v));
   }
 
-  // deleted optional<T&> constructors
+  // deleted optional<T&> constructors and additional optional<T&> constructors
 #    if _LIBCPP_STD_VER >= 26
-  template <class _Up, class... _Args, enable_if_t<is_constructible_v<_Tp, initializer_list<_Up>&, _Args...>, int> = 0>
-    requires __libcpp_opt_ref_ctor_deleted<_Up>
-  explicit optional(in_place_t, initializer_list<_Up>, _Args&&...) = delete;
-
+  // optional(U&&)
   template <class _Up = _Tp, enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_implicit<_Up>(), int> = 0>
     requires __libcpp_opt_ref_ctor_deleted<_Up>
   optional(_Up&&) = delete;
@@ -950,6 +971,21 @@ public:
     requires __libcpp_opt_ref_ctor_deleted<_Up>
   explicit optional(_Up&&) = delete;
 
+  // optional(optional<U>& rhs)
+  template <class _Up>
+    requires(!__libcpp_opt_ref_ctor_deleted<_Up>) && (!__is_std_optional<remove_cvref_t<_Tp>>::value) &&
+            (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, _Up&>
+  constexpr explicit(!is_convertible_v<_Up&, _Tp&>)
+      optional(optional<_Up>& __rhs) noexcept(is_nothrow_constructible_v<_Tp&, _Up&>) {
+    this->__construct_from(__rhs);
+  }
+
+  template <class _Up>
+    requires __libcpp_opt_ref_ctor_deleted<_Up> && (!__is_std_optional<remove_cvref_t<_Tp>>::value) &&
+                 (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, _Up&>
+  constexpr explicit optional(optional<_Up>& __rhs) noexcept = delete;
+
+  // optional(const optional<U>&)
   template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>::template __enable_implicit<_Up>(), int> = 0>
     requires __libcpp_opt_ref_ctor_deleted<_Up>
   optional(const optional<_Up>&) = delete;
@@ -958,6 +994,7 @@ public:
     requires __libcpp_opt_ref_ctor_deleted<_Up>
   explicit optional(const optional<_Up>&) = delete;
 
+  // optional(optional<U>&&)
   template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>::template __enable_implicit<_Up>(), int> = 0>
     requires __libcpp_opt_ref_ctor_deleted<_Up>
   optional(optional<_Up>&&) = delete;
@@ -965,6 +1002,20 @@ public:
   template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>::template __enable_explicit<_Up>(), int> = 0>
     requires __libcpp_opt_ref_ctor_deleted<_Up>
   explicit optional(optional<_Up>&&) = delete;
+
+  // optional(const optional<U>&&)
+  template <class _Up>
+    requires(!__libcpp_opt_ref_ctor_deleted<_Up>) && (!__is_std_optional<remove_cvref_t<_Tp>>::value) &&
+            (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, _Up>
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit(!is_convertible_v<const _Up, _Tp&>)
+      optional(const optional<_Up>&& __v) noexcept(is_nothrow_constructible_v<_Tp&, const _Up>) {
+    this->__construct_from(std::move(__v));
+  }
+
+  template <class _Up>
+    requires __libcpp_opt_ref_ctor_deleted<_Up> && (!__is_std_optional<remove_cvref_t<_Tp>>::value) &&
+                 (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, _Up>
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 optional(const optional<_Up>&& __v) noexcept = delete;
 #    endif
 
 #    if _LIBCPP_STD_VER >= 23
@@ -1013,8 +1064,12 @@ public:
     return *this;
   }
 
-  template <class... _Args, enable_if_t<is_constructible_v<_Tp, _Args...>, int> = 0>
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp& emplace(_Args&&... __args) {
+  template <class... _Args, enable_if_t<__is_constructible_for_optional_v<_Tp, _Args...>, int> = 0>
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp& emplace(_Args&&... __args)
+#    if _LIBCPP_STD_VER >= 26
+      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp, _Args...>)
+#    endif
+  {
     reset();
     this->__construct(std::forward<_Args>(__args)...);
     return this->__get();
@@ -1022,20 +1077,19 @@ public:
 
   template <class _Up,
             class... _Args,
-            enable_if_t<is_constructible_v<_Tp, initializer_list<_Up>&, _Args...>
-#    if _LIBCPP_STD_VER >= 26
-                            && !reference_constructs_from_temporary_v<_Tp&, _Up>
-#    endif
-                        ,
-                        int> = 0>
+            enable_if_t<__is_constructible_for_optional_initializer_list_v<_Tp, _Up, _Args...>, int> = 0>
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp& emplace(initializer_list<_Up> __il, _Args&&... __args) {
     reset();
     this->__construct(__il, std::forward<_Args>(__args)...);
     return this->__get();
   }
 
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void
-  swap(optional& __opt) noexcept(is_nothrow_move_constructible_v<_Tp> && is_nothrow_swappable_v<_Tp>) {
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void swap(optional& __opt) noexcept(
+      (is_nothrow_move_constructible_v<_Tp> && is_nothrow_swappable_v<_Tp>)
+#    if _LIBCPP_STD_VER >= 26
+      || is_lvalue_reference_v<_Tp>
+#    endif
+  ) {
     if (this->has_value() == __opt.has_value()) {
       if (this->has_value())
         this->__swap(__opt);
@@ -1050,32 +1104,56 @@ public:
     }
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<_Tp const> operator->() const noexcept {
+  _LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<_Tp const> operator->() const noexcept
+#    if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value");
     return std::addressof(this->__get());
   }
 
-  _LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<_Tp> operator->() noexcept {
+  _LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<_Tp> operator->() noexcept
+#    if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value");
     return std::addressof(this->__get());
   }
 
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& operator*() const& noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp& operator*() const& noexcept
+#    if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator* called on a disengaged value");
     return this->__get();
   }
 
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() & noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() & noexcept
+#    if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator* called on a disengaged value");
     return this->__get();
   }
 
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& operator*() && noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& operator*() && noexcept
+#    if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator* called on a disengaged value");
     return std::move(this->__get());
   }
 
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& operator*() const&& noexcept {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const _Tp&& operator*() const&& noexcept
+#    if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator* called on a disengaged value");
     return std::move(this->__get());
   }
@@ -1085,25 +1163,41 @@ public:
   using __base::__get;
   using __base::has_value;
 
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp const& value() const& {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp const& value() const&
+#    if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     if (!this->has_value())
       std::__throw_bad_optional_access();
     return this->__get();
   }
 
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& value() & {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& value() &
+#    if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     if (!this->has_value())
       std::__throw_bad_optional_access();
     return this->__get();
   }
 
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& value() && {
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp&& value() &&
+#    if _LIBCPP_STD_VER >= 26
+      requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     if (!this->has_value())
       std::__throw_bad_optional_access();
     return std::move(this->__get());
   }
 
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp const&& value() const&& {
+      [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp const&& value() const&&
+#    if _LIBCPP_STD_VER >= 26
+        requires(!is_lvalue_reference_v<_Tp>)
+#    endif
+  {
     if (!this->has_value())
       std::__throw_bad_optional_access();
     return std::move(this->__get());
@@ -1111,8 +1205,7 @@ public:
 
   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>>))
+    requires(!is_lvalue_reference_v<_Tp>)
 #    endif
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) const& {
     static_assert(is_copy_constructible_v<_Tp>, "optional<T>::value_or: T must be copy constructible");
@@ -1130,19 +1223,11 @@ public:
     return this->has_value() ? std::move(this->__get()) : static_cast<_Tp>(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>>))
-  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) && {
-    static_assert(is_move_constructible_v<_Tp>, "optional<T>::value_or: T must be move constructible");
-    static_assert(is_convertible_v<_Up, _Tp>, "optional<T>::value_or: U must be convertible to T");
-    return this->has_value() ? this->__get() : static_cast<_Tp>(std::forward<_Up>(__v));
-  }
-#    endif
-
 #    if _LIBCPP_STD_VER >= 23
   template <class _Func>
+#      if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#      endif
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) & {
     using _Up = invoke_result_t<_Func, _Tp&>;
     static_assert(__is_std_optional<remove_cvref_t<_Up>>::value,
@@ -1153,6 +1238,9 @@ public:
   }
 
   template <class _Func>
+#      if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#      endif
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const& {
     using _Up = invoke_result_t<_Func, const _Tp&>;
     static_assert(__is_std_optional<remove_cvref_t<_Up>>::value,
@@ -1163,6 +1251,9 @@ public:
   }
 
   template <class _Func>
+#      if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#      endif
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) && {
     using _Up = invoke_result_t<_Func, _Tp&&>;
     static_assert(__is_std_optional<remove_cvref_t<_Up>>::value,
@@ -1173,6 +1264,9 @@ public:
   }
 
   template <class _Func>
+#      if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#      endif
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const&& {
     using _Up = invoke_result_t<_Func, const _Tp&&>;
     static_assert(__is_std_optional<remove_cvref_t<_Up>>::value,
@@ -1183,6 +1277,9 @@ public:
   }
 
   template <class _Func>
+#      if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#      endif
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) & {
     using _Up = remove_cv_t<invoke_result_t<_Func, _Tp&>>;
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
@@ -1195,6 +1292,9 @@ public:
   }
 
   template <class _Func>
+#      if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#      endif
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) const& {
     using _Up = remove_cv_t<invoke_result_t<_Func, const _Tp&>>;
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
@@ -1207,6 +1307,9 @@ public:
   }
 
   template <class _Func>
+#      if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#      endif
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) && {
     using _Up = remove_cv_t<invoke_result_t<_Func, _Tp&&>>;
     static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
@@ -1219,8 +1322,11 @@ public:
   }
 
   template <class _Func>
+#      if _LIBCPP_STD_VER >= 26
+    requires(!is_lvalue_reference_v<_Tp>)
+#      endif
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto transform(_Func&& __f) const&& {
-    using _Up = remove_cvref_t<invoke_result_t<_Func, const _Tp&&>>;
+    using _Up = remove_cv_t<invoke_result_t<_Func, const _Tp&&>>;
     static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(std::move(value())) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(std::move(value())) should not be std::nullopt_t");
@@ -1233,6 +1339,9 @@ public:
   template <invocable _Func>
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr optional or_else(_Func&& __f) const&
     requires is_copy_constructible_v<_Tp>
+#      if _LIBCPP_STD_VER >= 26
+             && (!is_lvalue_reference_v<_Tp>)
+#      endif
   {
     static_assert(is_same_v<remove_cvref_t<invoke_result_t<_Func>>, optional>,
                   "Result of f() should be the same type as this optional");
@@ -1244,6 +1353,9 @@ public:
   template <invocable _Func>
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr optional or_else(_Func&& __f) &&
     requires is_move_constructible_v<_Tp>
+#      if _LIBCPP_STD_VER >= 26
+             && (!is_lvalue_reference_v<_Tp>)
+#      endif
   {
     static_assert(is_same_v<remove_cvref_t<invoke_result_t<_Func>>, optional>,
                   "Result of f() should be the same type as this optional");
@@ -1254,6 +1366,77 @@ public:
 #    endif // _LIBCPP_STD_VER >= 23
 
   using __base::reset;
+
+// optional<T&> overloads
+#    if _LIBCPP_STD_VER >= 26
+
+  _LIBCPP_HIDE_FROM_ABI constexpr add_pointer_t<_Tp> operator->() const noexcept
+    requires(is_lvalue_reference_v<_Tp>)
+  {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator-> called on a disengaged value");
+    return std::addressof(this->__get());
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& operator*() const noexcept
+    requires(is_lvalue_reference_v<_Tp>)
+  {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(this->has_value(), "optional operator* called on a disengaged value");
+    return this->__get();
+  }
+
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp& value() const
+    requires(is_lvalue_reference_v<_Tp>)
+  {
+    if (!this->has_value())
+      std::__throw_bad_optional_access();
+    return this->__get();
+  }
+
+  template <class _Up = remove_cvref_t<_Tp>>
+    requires(is_lvalue_reference_v<_Tp> && is_object_v<__libcpp_remove_reference_t<_Tp>> &&
+             !is_array_v<__libcpp_remove_reference_t<_Tp>>)
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decay_t<_Tp> value_or(_Up&& __v) const {
+    using _X = remove_cvref_t<_Tp>;
+    static_assert(is_constructible_v<_X, _Tp&>, "optional<T&>::value_or: remove_cv_t<T> must be constructible");
+    static_assert(is_convertible_v<_Up, _X>, "optional<T&>::value_or: U must be convertible to remove_cv_t<T>");
+    return this->has_value() ? this->__get() : static_cast<_X>(std::forward<_Up>(__v));
+  }
+
+  template <class _Func>
+    requires(is_lvalue_reference_v<_Tp>)
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const {
+    using _Up = invoke_result_t<_Func, _Tp&>;
+    static_assert(__is_std_optional<remove_cvref_t<_Up>>::value,
+                  "Result of f(value()) must be a specialization of std::optional");
+    if (*this)
+      return std::invoke(std::forward<_Func>(__f), value());
+    return remove_cvref_t<_Up>();
+  }
+
+  template <class _Func>
+    requires(is_lvalue_reference_v<_Tp...
[truncated]

@smallp-o-p

smallp-o-p commented Jan 6, 2026

Copy link
Copy Markdown
Contributor Author

I wonder if the current approach of having a common base class for optional<T> and optional<T&>, and not a partial specialization is still worth it given the amount of modifications needed to get our optional interface to be compatible with optional<T&>...

@github-actions

github-actions Bot commented Jan 6, 2026

Copy link
Copy Markdown

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

Comment thread libcxx/include/optional Outdated
@smallp-o-p smallp-o-p marked this pull request as ready for review January 6, 2026 20:55
@smallp-o-p

Copy link
Copy Markdown
Contributor Author

Also, in the case that this doesn't land by the time LLVM 22 branches, would this be eligible to be backported or is it better to just revert the optional<T&> commit(s) and re-implement it for LLVM 23?

@frederick-vs-ja

Copy link
Copy Markdown
Contributor

I think it will be better to should backport fixes to LLVM 22 branch.

Comment thread libcxx/test/support/copy_move_types.h Outdated
Comment thread libcxx/include/optional
Comment thread libcxx/include/optional Outdated
Comment thread libcxx/include/optional Outdated
Comment thread libcxx/include/optional Outdated
Comment thread libcxx/include/optional Outdated
Comment thread libcxx/include/optional Outdated
Comment thread libcxx/include/optional Outdated
Comment thread libcxx/test/support/copy_move_types.h Outdated
Comment thread libcxx/include/optional
@frederick-vs-ja frederick-vs-ja merged commit 9f5efd5 into llvm:main Jan 9, 2026
80 checks passed
Priyanshu3820 pushed a commit to Priyanshu3820/llvm-project that referenced this pull request Jan 18, 2026
Resolves llvm#174350

- Several issues were found in the current implementation of
`optional<T&>`
- `value()`, `operator*()`, `and_then()`, `transform()`, `operator->()`
still provided their ref-qualified versions for rvalues and `const&`.
- Using the listed methods on an rvalue `optional<T&>` would cause a
compile failure due to a mismatch in return types.
- On the latter, `operator*`, `operator->` would return `const` for a
`optional<T&>`, which is an incorrect deep const.
- A few constructors were missing (`optional<U>&`), and most
constructors relevant to `optional<T&>` were missing `noexcept`
- Constructors and `emplace` were not correctly constructing a `T&` as
specified in _`convert-ref-init-val`_
- Also corrects the behavior of `value_or` which should return
`remove_cv_t<T>` (in our case `decay_t<_Tp>`)
- Add several test cases to verify behavior, update `value_or` tests
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.

[libc++] std::optional<T&> non-conformance issues

4 participants