[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 Mar 31 02:02:40 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] [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;
 }
 



More information about the libcxx-commits mailing list