[libcxx-commits] [libcxx] [libc++] Implement LWG4406: value_or return statement is inconsistent with Mandates (PR #189568)
via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Mar 31 02:01:51 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Fernando Pelliccioni (fpelliccioni)
<details>
<summary>Changes</summary>
Implement the proposed resolution of [LWG4406](https://cplusplus.github.io/LWG/issue4406) for `optional::value_or` and `expected::value_or`.
Also applies [LWG3424](https://cplusplus.github.io/LWG/issue3424): return type is `remove_cv_t<T>` instead of `T`.
Note: neither LWG4406 nor LWG3424 have been formally adopted yet, but this is a speculative implementation (consistent with other "Not Adopted Yet" implementations in libc++).
Changes:
- Return type: `T` -> `remove_cv_t<T>`
- Remove `static_cast` from return statements (use implicit conversion to match Mandates)
- Update `static_assert` conditions from `is_copy/move_constructible` to `is_convertible_v`
Affects `optional<T>::value_or`, `optional<T&>::value_or`, and `expected<T,E>::value_or`.
Closes #<!-- -->189545
---
Full diff: https://github.com/llvm/llvm-project/pull/189568.diff
6 Files Affected:
- (modified) libcxx/docs/Status/Cxx2cIssues.csv (+1)
- (modified) libcxx/include/__expected/expected.h (+11-8)
- (modified) libcxx/include/optional (+18-15)
- (modified) libcxx/test/libcxx/utilities/expected/expected.expected/value_or.mandates.verify.cpp (+6-6)
- (modified) libcxx/test/std/utilities/expected/expected.expected/observers/value_or.pass.cpp (+15-2)
- (modified) libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp (+11)
``````````diff
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;
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/189568
More information about the libcxx-commits
mailing list