[libcxx-commits] [libcxx] [libc++] Disable mistakenly enabled `optional<T&>` constructors for `optional<T>` (PR #194446)
William Tran-Viet via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Apr 28 08:25:27 PDT 2026
https://github.com/smallp-o-p updated https://github.com/llvm/llvm-project/pull/194446
>From ffac919419ebfbc5d2f17cf3d5d420d7e262c400 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Mon, 27 Apr 2026 15:36:51 -0400
Subject: [PATCH 1/5] Disable optional<T&> constructor for optional<T>
---
libcxx/include/optional | 12 ++++++++----
.../optional.object.ctor/optional_U.pass.cpp | 5 +++++
.../optional/optional.object/optional_helper_types.h | 10 ++++++++++
3 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 9eed886aa4636..f1a2c5567a49a 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -893,6 +893,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 __libcpp_enable_opt_ref_ctor =
+ is_lvalue_reference_v<_Tp> && !reference_constructs_from_temporary_v<_Tp, _Up>;
# endif
// LWG2756: conditionally explicit conversion from _Up
@@ -1046,8 +1050,8 @@ 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 __libcpp_enable_opt_ref_ctor<_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);
@@ -1078,8 +1082,8 @@ 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 __libcpp_enable_opt_ref_ctor<_Up> && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
+ (!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));
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..ce86193f5ea4e 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
@@ -164,6 +164,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_helper_types.h b/libcxx/test/std/utilities/optional/optional.object/optional_helper_types.h
index 704d99acb15e9..e119a32bd5522 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
@@ -51,4 +51,14 @@ struct ReferenceConversionThrows {
}
};
+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;
+};
+
#endif
>From ef1f154c4029db3148a561e3db9df612c278ce60 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Mon, 27 Apr 2026 16:01:28 -0400
Subject: [PATCH 2/5] Add regression test for optional(const optional<U>&&)
---
.../optional.object.ctor/const_optional_U.pass.cpp | 2 ++
.../optional/optional.object/optional_helper_types.h | 10 ++++++++++
2 files changed, 12 insertions(+)
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..a3561cff0bc67 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,8 @@ constexpr bool test_ref() {
assert(*o2 == 1);
}
+ 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_helper_types.h b/libcxx/test/std/utilities/optional/optional.object/optional_helper_types.h
index e119a32bd5522..2b28c25568204 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
@@ -61,4 +61,14 @@ struct LValueOnly {
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
>From 9f3a719a0aa47b2b5a86ad8e5b268a5608f7199c Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Mon, 27 Apr 2026 22:39:31 -0400
Subject: [PATCH 3/5] Correct optional<T&> constructor noexcepts and U type
passed to delete check
---
libcxx/include/optional | 20 ++++++-------
.../const_optional_U.pass.cpp | 29 ++++++++++++++++++
.../optional.object.ctor/optional_U.pass.cpp | 30 +++++++++++++++++++
.../optional.object/optional_helper_types.h | 19 +++++++++++-
4 files changed, 87 insertions(+), 11 deletions(-)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index f1a2c5567a49a..e88b0aed262a4 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -1004,7 +1004,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);
@@ -1012,7 +1012,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);
@@ -1050,7 +1050,7 @@ public:
// optional(optional<U>& rhs)
template <class _Up>
- requires __libcpp_enable_opt_ref_ctor<_Up> && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
+ requires __libcpp_enable_opt_ref_ctor<_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&>) {
@@ -1058,17 +1058,17 @@ public:
}
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>&&)
@@ -1082,16 +1082,16 @@ public:
// optional(const optional<U>&&)
template <class _Up>
- requires __libcpp_enable_opt_ref_ctor<_Up> && (!is_same_v<remove_cvref_t<_Tp>, optional<_Up>>) &&
- (!is_same_v<_Tp&, _Up>) && is_constructible_v<_Tp&, _Up>
+ requires __libcpp_enable_opt_ref_ctor<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 a3561cff0bc67..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,35 @@ 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;
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 ce86193f5ea4e..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
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 2b28c25568204..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,14 @@ struct ReferenceConversionThrows {
return rvalue;
}
+
+ constexpr operator const T&() const&& {
+ if (throws) {
+ TEST_THROW(2);
+ }
+
+ return rvalue;
+ }
};
template <typename T>
>From a0314fd089f92e9e4c06deea414081662122da79 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Mon, 27 Apr 2026 23:04:03 -0400
Subject: [PATCH 4/5] Update ref_constructs_from_temporary verify
---
.../ref_constructs_from_temporary.verify.cpp | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
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>&)
>From 750ecce8dbb8e2fcb07f67f944f5c0efc0c6110f Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Tue, 28 Apr 2026 11:25:13 -0400
Subject: [PATCH 5/5] Rename enable boolean
---
libcxx/include/optional | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index e88b0aed262a4..f7e12f84beba0 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -895,7 +895,7 @@ private:
is_lvalue_reference_v<_Tp> && reference_constructs_from_temporary_v<_Tp, _Up>;
template <class _Up>
- constexpr static bool __libcpp_enable_opt_ref_ctor =
+ constexpr static bool __ref_ctor_enabled =
is_lvalue_reference_v<_Tp> && !reference_constructs_from_temporary_v<_Tp, _Up>;
# endif
@@ -1050,8 +1050,8 @@ public:
// optional(optional<U>& rhs)
template <class _Up>
- requires __libcpp_enable_opt_ref_ctor<_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);
@@ -1082,7 +1082,7 @@ public:
// optional(const optional<U>&&)
template <class _Up>
- requires __libcpp_enable_opt_ref_ctor<const _Up> && (!is_same_v<remove_cvref_t<_Tp>, optional<_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>) {
More information about the libcxx-commits
mailing list