Skip to content

release/22.x: [libc++] Disable mistakenly enabled optional<T&> constructors for optional<T> (#194446)#195472

Merged
dyung merged 1 commit into
llvm:release/22.xfrom
llvmbot:issue194446
Jun 15, 2026
Merged

release/22.x: [libc++] Disable mistakenly enabled optional<T&> constructors for optional<T> (#194446)#195472
dyung merged 1 commit into
llvm:release/22.xfrom
llvmbot:issue194446

Conversation

@llvmbot

@llvmbot llvmbot commented May 2, 2026

Copy link
Copy Markdown
Member

Backport 239189c

Requested by: @frederick-vs-ja

@llvmbot llvmbot requested a review from a team as a code owner May 2, 2026 17:51
@llvmbot llvmbot added this to the LLVM 22.x Release milestone May 2, 2026
@github-project-automation github-project-automation Bot moved this to Needs Triage in LLVM Release Status May 2, 2026
@llvmbot

llvmbot commented May 2, 2026

Copy link
Copy Markdown
Member Author

@frederick-vs-ja What do you think about merging this PR to the release branch?

@llvmbot llvmbot requested a review from frederick-vs-ja May 2, 2026 17:51
@llvmorg-github-actions llvmorg-github-actions Bot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label May 2, 2026
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-libcxx

Author: llvmbot

Changes

Backport 239189c

Requested by: @frederick-vs-ja


Full diff: https://github.com/llvm/llvm-project/pull/195472.diff

5 Files Affected:

  • (modified) libcxx/include/optional (+15-11)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/const_optional_U.pass.cpp (+31)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/optional_U.pass.cpp (+35)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ref_constructs_from_temporary.verify.cpp (+3-5)
  • (modified) libcxx/test/std/utilities/optional/optional.object/optional_helper_types.h (+38-1)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 12fbcdfa5c5d6..4d85ac1e0ff5e 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -828,6 +828,10 @@ private:
   template <class _Up>
   constexpr static bool __libcpp_opt_ref_ctor_deleted =
       is_lvalue_reference_v<_Tp> && reference_constructs_from_temporary_v<_Tp, _Up>;
+
+  template <class _Up>
+  constexpr static bool __ref_ctor_enabled =
+      is_lvalue_reference_v<_Tp> && !reference_constructs_from_temporary_v<_Tp, _Up>;
 #    endif
 
   // LWG2756: conditionally explicit conversion from _Up
@@ -935,7 +939,7 @@ public:
   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)
 #    if _LIBCPP_STD_VER >= 26
-      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up&>)
+      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, const _Up&>)
 #    endif
   {
     this->__construct_from(__v);
@@ -943,7 +947,7 @@ public:
   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)
 #    if _LIBCPP_STD_VER >= 26
-      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up&>)
+      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, const _Up&>)
 #    endif
   {
     this->__construct_from(__v);
@@ -981,25 +985,25 @@ public:
 
   // optional(optional<U>& rhs)
   template <class _Up>
-    requires(!__libcpp_opt_ref_ctor_deleted<_Up>) && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
-            (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, _Up&>
+    requires __ref_ctor_enabled<_Up&> && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) && (!is_same_v<_Tp&, _Up>) &&
+             is_constructible_v<_Tp&, _Up&>
   _LIBCPP_HIDE_FROM_ABI 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_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
+    requires __libcpp_opt_ref_ctor_deleted<_Up&> && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
                  (!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>
+    requires __libcpp_opt_ref_ctor_deleted<const _Up&>
   optional(const optional<_Up>&) = delete;
 
   template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>::template __enable_explicit<_Up>(), int> = 0>
-    requires __libcpp_opt_ref_ctor_deleted<_Up>
+    requires __libcpp_opt_ref_ctor_deleted<const _Up&>
   explicit optional(const optional<_Up>&) = delete;
 
   // optional(optional<U>&&)
@@ -1013,16 +1017,16 @@ public:
 
   // optional(const optional<U>&&)
   template <class _Up>
-    requires(!__libcpp_opt_ref_ctor_deleted<_Up>) && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
-            (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, _Up>
+    requires __ref_ctor_enabled<const _Up> && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
+             (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, const _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_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
-                 (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, _Up>
+    requires __libcpp_opt_ref_ctor_deleted<const _Up> && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
+                 (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, const _Up>
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 optional(const optional<_Up>&& __v) noexcept = delete;
 #    endif
 
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/const_optional_U.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/const_optional_U.pass.cpp
index 597110bcb54fe..844317c8570d9 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/const_optional_U.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/const_optional_U.pass.cpp
@@ -151,6 +151,37 @@ constexpr bool test_ref() {
     assert(*o2 == 1);
   }
 
+  {
+    const std::optional<int> o1{2};
+    std::optional<const int&> o2(o1);
+    assert(*o2 == 2);
+    assert(&(*o2) == &(*o1));
+  }
+
+  {
+    const std::optional<ReferenceConversion<int>> o1({1, 2});
+    std::optional<const int&> o2(o1);
+    assert(o2.has_value());
+    assert(&(*o2) == &o1->lvalue);
+    assert(*o2 == 1);
+  }
+
+  {
+    const std::optional<ReferenceConversion<int>> o1({1, 2});
+    std::optional<const int&> o2(std::move(o1));
+    assert(o2.has_value());
+    assert(&(*o2) == &o1->rvalue);
+    assert(*o2 == 2);
+  }
+
+  {
+    const std::optional<ReferenceConversionThrows<int>> o1({1, 2});
+    ASSERT_NOT_NOEXCEPT(std::optional<const int&>(o1));
+    ASSERT_NOT_NOEXCEPT(std::optional<const int&>(std::move(o1)));
+  }
+
+  static_assert(!std::is_constructible_v<std::optional<int>, const std::optional<ConstRValueOnly<int>>&&>);
+
   return true;
 }
 #endif
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/optional_U.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/optional_U.pass.cpp
index 39a80efa475f7..03082033cd4dc 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/optional_U.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/optional_U.pass.cpp
@@ -144,6 +144,36 @@ constexpr bool test_ref() {
     assert(*o2 == 1);
   }
 
+  {
+    std::optional<int> o1(1);
+    std::optional<const int&> o2(o1);
+    assert(o2.has_value());
+    assert(*o2 == 1);
+    assert(&(*o2) == &(*o1));
+  }
+
+  {
+    std::optional<ReferenceConversion<int>> o1({1, 2});
+    std::optional<const int&> o2(o1);
+    assert(o2.has_value());
+    assert(*o2 == 1);
+    assert(&(*o2) == &o1->lvalue);
+  }
+
+  {
+    std::optional<ReferenceConversion<int>> o1({1, 2});
+    std::optional<const int&> o2(std::move(o1));
+    assert(o2.has_value());
+    assert(*o2 == 2);
+    assert(&(*o2) == &o1->rvalue);
+  }
+
+  {
+    std::optional<ReferenceConversionThrows<int>> o1({1, 2});
+    ASSERT_NOT_NOEXCEPT(std::optional<const int&>(o1));
+    ASSERT_NOT_NOEXCEPT(std::optional<const int&>(std::move(o1)));
+  }
+
   return true;
 }
 #endif
@@ -164,6 +194,11 @@ int main(int, char**) {
     test<Z>(std::move(rhs), true);
   }
 
+#if TEST_STD_VER >= 26
+  // GH: #194415
+  static_assert(!std::is_constructible_v<std::optional<int>, std::optional<LValueOnly<int>>&>);
+#endif
+
   static_assert(!(std::is_constructible<optional<X>, optional<Z>>::value), "");
 
 #if TEST_STD_VER >= 26
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ref_constructs_from_temporary.verify.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ref_constructs_from_temporary.verify.cpp
index 53ac77a2e5620..d4c95c302d817 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ref_constructs_from_temporary.verify.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/ref_constructs_from_temporary.verify.cpp
@@ -29,12 +29,10 @@ void test() {
   const std::optional<int> co(1);
   std::optional<int> o0(1);
 
-  // expected-error-re@*:* 10 {{call to deleted constructor of 'std::optional<{{.*}}>'}}
+  // expected-error-re@*:* 8 {{call to deleted constructor of 'std::optional<{{.*}}>'}}
   std::optional<const int&> o1{1};             // optional(U&&)
-  std::optional<const int&> o2{o0};            // optional(optional<U>&)
-  std::optional<const int&> o3{co};            // optional(const optional<U>&)
-  std::optional<const int&> o4{std::move(o0)}; // optional(optional<U>&&&)
-  std::optional<const int&> o5{std::move(co)}; // optional(optional<U>&&&)
+  std::optional<const int&> o4{std::move(o0)}; // optional(optional<U>&&)
+  std::optional<const int&> o5{std::move(co)}; // optional(optional<U>&&)
 
   std::optional<const X&> o6{1};              // optional(U&&)
   std::optional<const X&> o7{o0};             // optional(optional<U>&)
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional_helper_types.h b/libcxx/test/std/utilities/optional/optional.object/optional_helper_types.h
index 704d99acb15e9..d9b17a0b91f3b 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional_helper_types.h
+++ b/libcxx/test/std/utilities/optional/optional.object/optional_helper_types.h
@@ -21,8 +21,9 @@ struct ReferenceConversion {
   constexpr ReferenceConversion(T lval, T rval) : lvalue(lval), rvalue(rval) {}
 
   constexpr operator T&() & noexcept { return lvalue; }
-
+  constexpr operator const T&() const& noexcept { return lvalue; }
   constexpr operator T&() && noexcept { return rvalue; }
+  constexpr operator const T&() const&& noexcept { return rvalue; }
 };
 
 template <typename T>
@@ -42,6 +43,14 @@ struct ReferenceConversionThrows {
     return lvalue;
   }
 
+  constexpr operator const T&() const& {
+    if (throws) {
+      TEST_THROW(1);
+    }
+
+    return lvalue;
+  }
+
   constexpr operator T&() && {
     if (throws) {
       TEST_THROW(2);
@@ -49,6 +58,34 @@ struct ReferenceConversionThrows {
 
     return rvalue;
   }
+
+  constexpr operator const T&() const&& {
+    if (throws) {
+      TEST_THROW(2);
+    }
+
+    return rvalue;
+  }
+};
+
+template <typename T>
+struct LValueOnly {
+  T val{};
+
+  constexpr operator T&() & noexcept { return val; }
+  constexpr operator T&() const&  = delete;
+  constexpr operator T&() &&      = delete;
+  constexpr operator T&() const&& = delete;
+};
+
+template <typename T>
+struct ConstRValueOnly {
+  mutable T val{};
+
+  constexpr operator T&() &      = delete;
+  constexpr operator T&() const& = delete;
+  constexpr operator T&() &&     = delete;
+  constexpr operator T&() const&& { return val; };
 };
 
 #endif

@llvmbot llvmbot force-pushed the issue194446 branch 2 times, most recently from e9556ad to 141ebc5 Compare May 2, 2026 17:58

@frederick-vs-ja frederick-vs-ja left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM as-is. Although I have no idea why CI doesn't run yet.

@github-project-automation github-project-automation Bot moved this from Needs Triage to Needs Merge in LLVM Release Status May 3, 2026
@dyung

dyung commented May 4, 2026

Copy link
Copy Markdown
Contributor

@ldionne as the maintainer for libcxx, what do you think about taking this fix in the 22.x release branch? I'm not too familiar with libcxx, so would like the opinion of a maintainer whether this is a critical bug fix or serious enough that it should be backported.

@dyung

dyung commented May 13, 2026

Copy link
Copy Markdown
Contributor

ping @ldionne

@dyung

dyung commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

ping @ldionne, do you approve of this change being backported to 22.x?

@dyung

dyung commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

ping. We have only less than a week before the 22.x branch is closed.

@ldionne ldionne left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Apologies for missing the repeated pings. I think we should cherry-pick this change.

…optional<T>` (llvm#194446)

Resolves llvm#194415

- A constructor specifically meant for `optional<T&>` was left enabled
for `optional<T>`
- Fix it, and add a test to check for regression.
- This patch also corrects the constraints for `optional(optional<U>&)`
and `optional(const optional<U>&)` , as they were incorrectly
disallowing [valid conversions](https://godbolt.org/z/1r5Ea7z5M)
- Also, correct the `noexcept` specification.
- Add tests for both corrections.

(cherry picked from commit 239189c)
@dyung dyung merged commit ca7933e into llvm:release/22.x Jun 15, 2026
1 of 2 checks passed
@github-project-automation github-project-automation Bot moved this from Needs Merge to Done in LLVM Release Status Jun 15, 2026
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

Development

Successfully merging this pull request may close these issues.

5 participants