[libcxx-commits] [libcxx] [libc++] Implement LWG4406: value_or return statement is inconsistent with Mandates (PR #189568)
Fernando Pelliccioni via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Apr 7 11:01:51 PDT 2026
https://github.com/fpelliccioni updated https://github.com/llvm/llvm-project/pull/189568
>From fdb2235360db5612f54740496ff83dfc2e46f178 Mon Sep 17 00:00:00 2001
From: Fernando Pelliccioni <fpelliccioni at gmail.com>
Date: Tue, 31 Mar 2026 09:13:55 +0200
Subject: [PATCH 1/3] [libc++] Implement LWG4406: value_or return statement is
inconsistent with Mandates
Adjust optional::value_or and expected::value_or to not perform explicit
conversions (static_cast), so that the actual conversion performed
matches the Mandates requirements (implicit convertibility).
Also apply LWG3424: use remove_cv_t<T> as the return type instead of T.
Changes:
- Return type: T -> remove_cv_t<T>
- Remove static_cast from return statements
- Change static_assert conditions from is_copy/move_constructible
to is_convertible_v
---
libcxx/docs/Status/Cxx2cIssues.csv | 1 +
libcxx/include/__expected/expected.h | 19 ++++++-----
libcxx/include/optional | 33 ++++++++++---------
.../value_or.mandates.verify.cpp | 12 +++----
.../observers/value_or.pass.cpp | 17 ++++++++--
.../optional.object.observe/value_or.pass.cpp | 11 +++++++
6 files changed, 62 insertions(+), 31 deletions(-)
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 60b1bd6ff70da..6563478ef451d 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -263,4 +263,5 @@
"`LWG4139 <https://wg21.link/LWG4139>`__","§[time.zone.leap] recursive constraint in ``<=>``","Not Adopted Yet","|Complete|","20","`#118369 <https://github.com/llvm/llvm-project/issues/118369>`__",""
"`LWG3456 <https://wg21.link/LWG3456>`__","Pattern used by ``std::from_chars`` is underspecified (option B)","Not Adopted Yet","|Complete|","20","`#118370 <https://github.com/llvm/llvm-project/issues/118370>`__",""
"`LWG3882 <https://wg21.link/LWG3882>`__","``tuple`` relational operators have confused friendships","Not Adopted Yet","|Complete|","22","","The comparison operators are constrained harder than the proposed resolution. libstdc++ and MSVC STL do the same."
+"`LWG4406 <https://wg21.link/LWG4406>`__","``value_or`` return statement is inconsistent with *Mandates*","Not Adopted Yet","|Complete|","23","`#189545 <https://github.com/llvm/llvm-project/issues/189545>`__","Also applies LWG3424."
"","","","","","",""
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 24ae33d4e3af8..0b7e05095c22d 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -884,18 +884,21 @@ class expected : private __expected_base<_Tp, _Err> {
return std::move(this->__unex());
}
+ // LWG4406 value_or return statement is inconsistent with Mandates
template <class _Up = remove_cv_t<_Tp>>
- [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) const& {
- static_assert(is_copy_constructible_v<_Tp>, "value_type has to be copy constructible");
- static_assert(is_convertible_v<_Up, _Tp>, "argument has to be convertible to value_type");
- return this->__has_val() ? this->__val() : static_cast<_Tp>(std::forward<_Up>(__v));
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr remove_cv_t<_Tp> value_or(_Up&& __v) const& {
+ using _Ret = remove_cv_t<_Tp>;
+ static_assert(is_convertible_v<const _Tp&, _Ret>, "value_type has to be implicitly convertible to remove_cv_t<T>");
+ static_assert(is_convertible_v<_Up, _Ret>, "argument has to be convertible to value_type");
+ return this->__has_val() ? this->__val() : std::forward<_Up>(__v);
}
template <class _Up = remove_cv_t<_Tp>>
- [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) && {
- static_assert(is_move_constructible_v<_Tp>, "value_type has to be move constructible");
- static_assert(is_convertible_v<_Up, _Tp>, "argument has to be convertible to value_type");
- return this->__has_val() ? std::move(this->__val()) : static_cast<_Tp>(std::forward<_Up>(__v));
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr remove_cv_t<_Tp> value_or(_Up&& __v) && {
+ using _Ret = remove_cv_t<_Tp>;
+ static_assert(is_convertible_v<_Tp, _Ret>, "value_type has to be implicitly convertible to remove_cv_t<T>");
+ static_assert(is_convertible_v<_Up, _Ret>, "argument has to be convertible to value_type");
+ return this->__has_val() ? std::move(this->__val()) : std::forward<_Up>(__v);
}
template <class _Up = _Err>
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 9eed886aa4636..5956ac4fc7b01 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 = remove_cv_t<T>> constexpr T value_or(U &&) const &;
- template<class U = remove_cv_t<T>> constexpr T value_or(U &&) &&;
+ template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U &&) const &; // LWG4406
+ template<class U = remove_cv_t<T>> constexpr remove_cv_t<T> value_or(U &&) &&; // LWG4406
// [optional.monadic], monadic operations
template<class F> constexpr auto and_then(F&& f) &; // since C++23
@@ -1176,24 +1176,27 @@ public:
using __base::has_value;
using __base::value;
+ // LWG4406 value_or return statement is inconsistent with Mandates
template <class _Up = remove_cv_t<_Tp>>
# if _LIBCPP_STD_VER >= 26
requires(is_object_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");
- 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));
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr remove_cv_t<_Tp> value_or(_Up&& __v) const& {
+ using _Ret = remove_cv_t<_Tp>;
+ static_assert(is_convertible_v<const _Tp&, _Ret>, "optional<T>::value_or: T must be copy constructible");
+ static_assert(is_convertible_v<_Up, _Ret>, "optional<T>::value_or: U must be convertible to T");
+ return this->has_value() ? this->__get() : std::forward<_Up>(__v);
}
template <class _Up = remove_cv_t<_Tp>>
# if _LIBCPP_STD_VER >= 26
requires(is_object_v<_Tp>)
# endif
- [[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() ? std::move(this->__get()) : static_cast<_Tp>(std::forward<_Up>(__v));
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr remove_cv_t<_Tp> value_or(_Up&& __v) && {
+ using _Ret = remove_cv_t<_Tp>;
+ static_assert(is_convertible_v<_Tp, _Ret>, "optional<T>::value_or: T must be move constructible");
+ static_assert(is_convertible_v<_Up, _Ret>, "optional<T>::value_or: U must be convertible to T");
+ return this->has_value() ? std::move(this->__get()) : std::forward<_Up>(__v);
}
# if _LIBCPP_STD_VER >= 23
@@ -1343,15 +1346,15 @@ public:
// optional<T&> overloads
# if _LIBCPP_STD_VER >= 26
+ // LWG4406 value_or return statement is inconsistent with Mandates
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 {
- static_assert(
- is_constructible_v<remove_cvref_t<_Tp>, _Tp&>, "optional<T&>::value_or: remove_cv_t<T> must be constructible");
- static_assert(
- is_convertible_v<_Up, remove_cvref_t<_Tp>>, "optional<T&>::value_or: U must be convertible to remove_cv_t<T>");
- return this->has_value() ? this->__get() : static_cast<remove_cvref_t<_Tp>>(std::forward<_Up>(__v));
+ using _Ret = remove_cvref_t<_Tp>;
+ static_assert(is_convertible_v<_Tp&, _Ret>, "optional<T&>::value_or: T must be constructible");
+ static_assert(is_convertible_v<_Up, _Ret>, "optional<T&>::value_or: U must be convertible to remove_cv_t<T>");
+ return this->has_value() ? this->__get() : std::forward<_Up>(__v);
}
template <class _Func>
diff --git a/libcxx/test/libcxx/utilities/expected/expected.expected/value_or.mandates.verify.cpp b/libcxx/test/libcxx/utilities/expected/expected.expected/value_or.mandates.verify.cpp
index 75c541c194b59..92d718a986ae2 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.expected/value_or.mandates.verify.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.expected/value_or.mandates.verify.cpp
@@ -10,11 +10,11 @@
// ADDITIONAL_COMPILE_FLAGS: -Xclang -verify-ignore-unexpected=error
// Test the mandates
-// template<class U> constexpr T value_or(U&& v) const &;
-// Mandates: is_copy_constructible_v<T> is true and is_convertible_v<U, T> is true.
+// template<class U> constexpr remove_cv_t<T> value_or(U&& v) const &;
+// Mandates: is_convertible_v<const T&, remove_cv_t<T>> is true and is_convertible_v<U, remove_cv_t<T>> is true.
-// template<class U> constexpr T value_or(U&& v) &&;
-// Mandates: is_move_constructible_v<T> is true and is_convertible_v<U, T> is true.
+// template<class U> constexpr remove_cv_t<T> value_or(U&& v) &&;
+// Mandates: is_convertible_v<T, remove_cv_t<T>> is true and is_convertible_v<U, remove_cv_t<T>> is true.
#include <expected>
#include <utility>
@@ -38,7 +38,7 @@ void test() {
const std::expected<NonCopyable, int> f1{5};
// expected-note at +1 {{in instantiation of function template specialization 'std::expected<NonCopyable, int>::value_or<int>' requested here}}
(void)f1.value_or(5);
- // expected-error-re@*:* {{static assertion failed {{.*}}value_type has to be copy constructible}}
+ // expected-error-re@*:* {{static assertion failed {{.*}}value_type has to be implicitly convertible}}
}
// const & overload
@@ -56,7 +56,7 @@ void test() {
std::expected<NonMovable, int> f1{5};
// expected-note at +1 {{in instantiation of function template specialization 'std::expected<NonMovable, int>::value_or<int>' requested here}}
(void)std::move(f1).value_or(5);
- //expected-error-re@*:* {{static assertion failed {{.*}}value_type has to be move constructible}}
+ //expected-error-re@*:* {{static assertion failed {{.*}}value_type has to be implicitly convertible}}
}
// && overload
diff --git a/libcxx/test/std/utilities/expected/expected.expected/observers/value_or.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/observers/value_or.pass.cpp
index 82bcd7be4c432..f49df4ccc22e1 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/observers/value_or.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/observers/value_or.pass.cpp
@@ -8,8 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
-// template<class U> constexpr T value_or(U&& v) const &;
-// template<class U> constexpr T value_or(U&& v) &&;
+// template<class U> constexpr remove_cv_t<T> value_or(U&& v) const &;
+// template<class U> constexpr remove_cv_t<T> value_or(U&& v) &&;
#include <cassert>
#include <concepts>
@@ -49,6 +49,19 @@ constexpr bool test() {
assert(x == 10);
}
+ // LWG3424: return type is remove_cv_t<T>
+ {
+ const std::expected<const int, int> e(5);
+ std::same_as<int> decltype(auto) x = e.value_or(10);
+ assert(x == 5);
+ }
+
+ {
+ std::expected<const int, int> e(std::unexpect, 5);
+ std::same_as<int> decltype(auto) x = std::move(e).value_or(10);
+ assert(x == 10);
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
index f4ad896ed9816..b8ad9270ca94d 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
@@ -89,6 +89,17 @@ constexpr int test()
assert(!opt);
}
#endif
+ // LWG3424: return type is remove_cv_t<T>
+ {
+ optional<const int> opt(2);
+ ASSERT_SAME_TYPE(decltype(opt.value_or(3)), int);
+ assert(opt.value_or(3) == 2);
+ }
+ {
+ optional<const int> opt;
+ ASSERT_SAME_TYPE(decltype(std::move(opt).value_or(3)), int);
+ assert(std::move(opt).value_or(3) == 3);
+ }
return 0;
}
>From 49a170f8764490a6441ef8e4b5e7636e23c3355e Mon Sep 17 00:00:00 2001
From: Fernando Pelliccioni <fpelliccioni at gmail.com>
Date: Tue, 7 Apr 2026 19:51:41 +0200
Subject: [PATCH 2/3] [libc++] Address review comments for LWG4406
- Add libcxx-specific verify.cpp for optional::value_or mandates.
- Update expected::value_or verify.cpp and align static_assert
wording in both headers with the new is_convertible_v check.
---
libcxx/include/__expected/expected.h | 12 ++-
libcxx/include/optional | 18 ++--
.../value_or.mandates.verify.cpp | 8 +-
.../value_or.mandates.verify.cpp | 100 ++++++++++++++++++
4 files changed, 124 insertions(+), 14 deletions(-)
create mode 100644 libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.mandates.verify.cpp
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 0b7e05095c22d..e234dbe586bb3 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -888,16 +888,20 @@ class expected : private __expected_base<_Tp, _Err> {
template <class _Up = remove_cv_t<_Tp>>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr remove_cv_t<_Tp> value_or(_Up&& __v) const& {
using _Ret = remove_cv_t<_Tp>;
- static_assert(is_convertible_v<const _Tp&, _Ret>, "value_type has to be implicitly convertible to remove_cv_t<T>");
- static_assert(is_convertible_v<_Up, _Ret>, "argument has to be convertible to value_type");
+ static_assert(is_convertible_v<const _Tp&, _Ret>,
+ "expected::value_or: const T& must be implicitly convertible to remove_cv_t<T>");
+ static_assert(is_convertible_v<_Up, _Ret>,
+ "expected::value_or: U must be implicitly convertible to remove_cv_t<T>");
return this->__has_val() ? this->__val() : std::forward<_Up>(__v);
}
template <class _Up = remove_cv_t<_Tp>>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr remove_cv_t<_Tp> value_or(_Up&& __v) && {
using _Ret = remove_cv_t<_Tp>;
- static_assert(is_convertible_v<_Tp, _Ret>, "value_type has to be implicitly convertible to remove_cv_t<T>");
- static_assert(is_convertible_v<_Up, _Ret>, "argument has to be convertible to value_type");
+ static_assert(is_convertible_v<_Tp, _Ret>,
+ "expected::value_or: T must be implicitly convertible to remove_cv_t<T>");
+ static_assert(is_convertible_v<_Up, _Ret>,
+ "expected::value_or: U must be implicitly convertible to remove_cv_t<T>");
return this->__has_val() ? std::move(this->__val()) : std::forward<_Up>(__v);
}
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 5956ac4fc7b01..ccaec5dc1e475 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -1183,8 +1183,10 @@ public:
# endif
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr remove_cv_t<_Tp> value_or(_Up&& __v) const& {
using _Ret = remove_cv_t<_Tp>;
- static_assert(is_convertible_v<const _Tp&, _Ret>, "optional<T>::value_or: T must be copy constructible");
- static_assert(is_convertible_v<_Up, _Ret>, "optional<T>::value_or: U must be convertible to T");
+ static_assert(is_convertible_v<const _Tp&, _Ret>,
+ "optional<T>::value_or: const T& must be implicitly convertible to remove_cv_t<T>");
+ static_assert(is_convertible_v<_Up, _Ret>,
+ "optional<T>::value_or: U must be implicitly convertible to remove_cv_t<T>");
return this->has_value() ? this->__get() : std::forward<_Up>(__v);
}
@@ -1194,8 +1196,10 @@ public:
# endif
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr remove_cv_t<_Tp> value_or(_Up&& __v) && {
using _Ret = remove_cv_t<_Tp>;
- static_assert(is_convertible_v<_Tp, _Ret>, "optional<T>::value_or: T must be move constructible");
- static_assert(is_convertible_v<_Up, _Ret>, "optional<T>::value_or: U must be convertible to T");
+ static_assert(is_convertible_v<_Tp, _Ret>,
+ "optional<T>::value_or: T must be implicitly convertible to remove_cv_t<T>");
+ static_assert(is_convertible_v<_Up, _Ret>,
+ "optional<T>::value_or: U must be implicitly convertible to remove_cv_t<T>");
return this->has_value() ? std::move(this->__get()) : std::forward<_Up>(__v);
}
@@ -1352,8 +1356,10 @@ public:
!is_array_v<__libcpp_remove_reference_t<_Tp>>)
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decay_t<_Tp> value_or(_Up&& __v) const {
using _Ret = remove_cvref_t<_Tp>;
- static_assert(is_convertible_v<_Tp&, _Ret>, "optional<T&>::value_or: T must be constructible");
- static_assert(is_convertible_v<_Up, _Ret>, "optional<T&>::value_or: U must be convertible to remove_cv_t<T>");
+ static_assert(is_convertible_v<_Tp&, _Ret>,
+ "optional<T&>::value_or: T& must be implicitly convertible to remove_cv_t<T>");
+ static_assert(is_convertible_v<_Up, _Ret>,
+ "optional<T&>::value_or: U must be implicitly convertible to remove_cv_t<T>");
return this->has_value() ? this->__get() : std::forward<_Up>(__v);
}
diff --git a/libcxx/test/libcxx/utilities/expected/expected.expected/value_or.mandates.verify.cpp b/libcxx/test/libcxx/utilities/expected/expected.expected/value_or.mandates.verify.cpp
index 92d718a986ae2..c4f3834efff16 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.expected/value_or.mandates.verify.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.expected/value_or.mandates.verify.cpp
@@ -38,7 +38,7 @@ void test() {
const std::expected<NonCopyable, int> f1{5};
// expected-note at +1 {{in instantiation of function template specialization 'std::expected<NonCopyable, int>::value_or<int>' requested here}}
(void)f1.value_or(5);
- // expected-error-re@*:* {{static assertion failed {{.*}}value_type has to be implicitly convertible}}
+ // expected-error-re@*:* {{static assertion failed {{.*}}must be implicitly convertible to remove_cv_t<T>}}
}
// const & overload
@@ -47,7 +47,7 @@ void test() {
const std::expected<NotConvertibleFromInt, int> f1{std::in_place};
// expected-note at +1 {{in instantiation of function template specialization 'std::expected<NotConvertibleFromInt, int>::value_or<int>' requested here}}
(void)f1.value_or(5);
- //expected-error-re@*:* {{static assertion failed {{.*}}argument has to be convertible to value_type}}
+ //expected-error-re@*:* {{static assertion failed {{.*}}U must be implicitly convertible to remove_cv_t<T>}}
}
// && overload
@@ -56,7 +56,7 @@ void test() {
std::expected<NonMovable, int> f1{5};
// expected-note at +1 {{in instantiation of function template specialization 'std::expected<NonMovable, int>::value_or<int>' requested here}}
(void)std::move(f1).value_or(5);
- //expected-error-re@*:* {{static assertion failed {{.*}}value_type has to be implicitly convertible}}
+ //expected-error-re@*:* {{static assertion failed {{.*}}must be implicitly convertible to remove_cv_t<T>}}
}
// && overload
@@ -65,6 +65,6 @@ void test() {
std::expected<NotConvertibleFromInt, int> f1{std::in_place};
// expected-note at +1 {{in instantiation of function template specialization 'std::expected<NotConvertibleFromInt, int>::value_or<int>' requested here}}
(void)std::move(f1).value_or(5);
- //expected-error-re@*:* {{static assertion failed {{.*}}argument has to be convertible to value_type}}
+ //expected-error-re@*:* {{static assertion failed {{.*}}U must be implicitly convertible to remove_cv_t<T>}}
}
}
diff --git a/libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.mandates.verify.cpp b/libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.mandates.verify.cpp
new file mode 100644
index 0000000000000..cd24a6b23b602
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/optional/optional.object/optional.object.observe/value_or.mandates.verify.cpp
@@ -0,0 +1,100 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14
+// ADDITIONAL_COMPILE_FLAGS: -Xclang -verify-ignore-unexpected=error
+
+// Test the mandates (LWG4406 / LWG3424)
+// template<class U> constexpr remove_cv_t<T> optional<T>::value_or(U&& v) const &;
+// Mandates: is_convertible_v<const T&, remove_cv_t<T>> is true
+// and is_convertible_v<U, remove_cv_t<T>> is true.
+//
+// template<class U> constexpr remove_cv_t<T> optional<T>::value_or(U&& v) &&;
+// Mandates: is_convertible_v<T, remove_cv_t<T>> is true
+// and is_convertible_v<U, remove_cv_t<T>> is true.
+//
+// template<class U> constexpr remove_cv_t<T> optional<T&>::value_or(U&& v) const; // since C++26
+// Mandates: is_convertible_v<T&, remove_cv_t<T>> is true
+// and is_convertible_v<U, remove_cv_t<T>> is true.
+
+#include <optional>
+#include <utility>
+
+#include "test_macros.h"
+
+struct NonCopyable {
+ NonCopyable(int) {}
+ NonCopyable(const NonCopyable&) = delete;
+};
+
+struct NonMovable {
+ NonMovable(int) {}
+ NonMovable(NonMovable&&) = delete;
+};
+
+struct NotConvertibleFromInt {};
+
+void test() {
+ // const& overload
+ // !is_convertible_v<const T&, remove_cv_t<T>>
+ {
+ const std::optional<NonCopyable> o{5};
+ // expected-note at +1 {{in instantiation of function template specialization 'std::optional<NonCopyable>::value_or<int>' requested here}}
+ (void)o.value_or(5);
+ // expected-error-re@*:* {{static assertion failed {{.*}}const T& must be implicitly convertible to remove_cv_t<T>}}
+ }
+
+ // const& overload
+ // !is_convertible_v<U, remove_cv_t<T>>
+ {
+ const std::optional<NotConvertibleFromInt> o{std::in_place};
+ // expected-note at +1 {{in instantiation of function template specialization 'std::optional<NotConvertibleFromInt>::value_or<int>' requested here}}
+ (void)o.value_or(5);
+ // expected-error-re@*:* {{static assertion failed {{.*}}optional<T>::value_or: U must be implicitly convertible to remove_cv_t<T>}}
+ }
+
+ // && overload
+ // !is_convertible_v<T, remove_cv_t<T>>
+ {
+ std::optional<NonMovable> o{5};
+ // expected-note at +1 {{in instantiation of function template specialization 'std::optional<NonMovable>::value_or<int>' requested here}}
+ (void)std::move(o).value_or(5);
+ // expected-error-re@*:* {{static assertion failed {{.*}}optional<T>::value_or: T must be implicitly convertible to remove_cv_t<T>}}
+ }
+
+ // && overload
+ // !is_convertible_v<U, remove_cv_t<T>>
+ {
+ std::optional<NotConvertibleFromInt> o{std::in_place};
+ // expected-note at +1 {{in instantiation of function template specialization 'std::optional<NotConvertibleFromInt>::value_or<int>' requested here}}
+ (void)std::move(o).value_or(5);
+ // expected-error-re@*:* {{static assertion failed {{.*}}optional<T>::value_or: U must be implicitly convertible to remove_cv_t<T>}}
+ }
+
+#if TEST_STD_VER >= 26
+ // optional<T&>::value_or
+ // !is_convertible_v<T&, remove_cv_t<T>>
+ {
+ NonCopyable nc{5};
+ std::optional<NonCopyable&> o(nc);
+ // expected-note at +1 {{in instantiation of function template specialization 'std::optional<NonCopyable &>::value_or<int>' requested here}}
+ (void)o.value_or(5);
+ // expected-error-re@*:* {{static assertion failed {{.*}}T& must be implicitly convertible to remove_cv_t<T>}}
+ }
+
+ // optional<T&>::value_or
+ // !is_convertible_v<U, remove_cv_t<T>>
+ {
+ NotConvertibleFromInt nci;
+ std::optional<NotConvertibleFromInt&> o(nci);
+ // expected-note at +1 {{in instantiation of function template specialization 'std::optional<NotConvertibleFromInt &>::value_or<int>' requested here}}
+ (void)o.value_or(5);
+ // expected-error-re@*:* {{static assertion failed {{.*}}optional<T&>::value_or: U must be implicitly convertible to remove_cv_t<T>}}
+ }
+#endif
+}
>From c0b49fb1f4e90563e9817ca670509d63d82a9940 Mon Sep 17 00:00:00 2001
From: Fernando Pelliccioni <fpelliccioni at gmail.com>
Date: Tue, 7 Apr 2026 20:01:35 +0200
Subject: [PATCH 3/3] [libc++] Add LWG3424 entry to Cxx2cIssues.csv
---
libcxx/docs/Status/Cxx2cIssues.csv | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 6563478ef451d..aca95567bf3d1 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -263,5 +263,6 @@
"`LWG4139 <https://wg21.link/LWG4139>`__","§[time.zone.leap] recursive constraint in ``<=>``","Not Adopted Yet","|Complete|","20","`#118369 <https://github.com/llvm/llvm-project/issues/118369>`__",""
"`LWG3456 <https://wg21.link/LWG3456>`__","Pattern used by ``std::from_chars`` is underspecified (option B)","Not Adopted Yet","|Complete|","20","`#118370 <https://github.com/llvm/llvm-project/issues/118370>`__",""
"`LWG3882 <https://wg21.link/LWG3882>`__","``tuple`` relational operators have confused friendships","Not Adopted Yet","|Complete|","22","","The comparison operators are constrained harder than the proposed resolution. libstdc++ and MSVC STL do the same."
-"`LWG4406 <https://wg21.link/LWG4406>`__","``value_or`` return statement is inconsistent with *Mandates*","Not Adopted Yet","|Complete|","23","`#189545 <https://github.com/llvm/llvm-project/issues/189545>`__","Also applies LWG3424."
+"`LWG4406 <https://wg21.link/LWG4406>`__","``value_or`` return statement is inconsistent with *Mandates*","Not Adopted Yet","|Complete|","23","`#189545 <https://github.com/llvm/llvm-project/issues/189545>`__",""
+"`LWG3424 <https://wg21.link/LWG3424>`__","``optional::value_or`` should never return a cv-qualified type","Not Adopted Yet","|Complete|","23","`#189545 <https://github.com/llvm/llvm-project/issues/189545>`__","Resolved by LWG4406."
"","","","","","",""
More information about the libcxx-commits
mailing list