[libcxx-commits] [libcxx] 9f67143 - [libc++] Implement LWG3938 (Cannot use std::expected monadic ops with move-only error_type)

via libcxx-commits libcxx-commits at lists.llvm.org
Wed Sep 6 07:38:59 PDT 2023


Author: yronglin
Date: 2023-09-06T22:38:29+08:00
New Revision: 9f67143be01c97cf80f6e96e7b4c172d6dc0690e

URL: https://github.com/llvm/llvm-project/commit/9f67143be01c97cf80f6e96e7b4c172d6dc0690e
DIFF: https://github.com/llvm/llvm-project/commit/9f67143be01c97cf80f6e96e7b4c172d6dc0690e.diff

LOG: [libc++] Implement LWG3938 (Cannot use std::expected monadic ops with move-only error_type)

Implement LWG3938 (Cannot use std::expected monadic ops with move-only error_type)
https://wg21.link/LWG3938

Reviewed By: #libc, ldionne

Differential Revision: https://reviews.llvm.org/D154116

Added: 
    

Modified: 
    libcxx/docs/Status/Cxx2cIssues.csv
    libcxx/include/__expected/expected.h
    libcxx/test/libcxx/utilities/expected/expected.expected/and_then.mandates.verify.cpp
    libcxx/test/std/utilities/expected/expected.expected/monadic/and_then.pass.cpp
    libcxx/test/std/utilities/expected/expected.expected/monadic/or_else.pass.cpp
    libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
    libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
    libcxx/test/std/utilities/expected/types.h

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 09c0706bb51d4a..a928b69ea10c48 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -14,7 +14,7 @@
 "`3925 <https://wg21.link/LWG3925>`__","Concept ``formattable``'s definition is incorrect","Varna June 2023","|Complete|","17.0","|format|"
 "`3927 <https://wg21.link/LWG3927>`__","Unclear preconditions for ``operator[]`` for sequence containers","Varna June 2023","|Nothing To Do|","",""
 "`3935 <https://wg21.link/LWG3935>`__","``template<class X> constexpr complex& operator=(const complex<X>&)`` has no specification","Varna June 2023","|Complete|","3.4",""
-"`3938 <https://wg21.link/LWG3938>`__","Cannot use ``std::expected`` monadic ops with move-only ``error_type``","Varna June 2023","","",""
+"`3938 <https://wg21.link/LWG3938>`__","Cannot use ``std::expected`` monadic ops with move-only ``error_type``","Varna June 2023","|Complete|","18.0",""
 "`3940 <https://wg21.link/LWG3940>`__","``std::expected<void, E>::value()`` also needs ``E`` to be copy constructible","Varna June 2023","","",""
 "","","","","",""
 "`3343 <https://wg21.link/LWG3343>`__","Ordering of calls to ``unlock()`` and ``notify_all()`` in Effects element of ``notify_all_at_thread_exit()`` should be reversed","Not Yet Adopted","|Complete|","16.0",""

diff  --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index dd383ff8596370..eaf5bc8d6b38ff 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -651,11 +651,11 @@ class expected {
     requires is_constructible_v<_Err, _Err&>
   _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) & {
     using _Up = remove_cvref_t<invoke_result_t<_Func, _Tp&>>;
-    static_assert(__is_std_expected<_Up>::value, "The result of f(value()) must be a specialization of std::expected");
+    static_assert(__is_std_expected<_Up>::value, "The result of f(**this) must be a specialization of std::expected");
     static_assert(is_same_v<typename _Up::error_type, _Err>,
-                  "The result of f(value()) must have the same error_type as this expected");
+                  "The result of f(**this) must have the same error_type as this expected");
     if (has_value()) {
-      return std::invoke(std::forward<_Func>(__f), value());
+      return std::invoke(std::forward<_Func>(__f), __union_.__val_);
     }
     return _Up(unexpect, error());
   }
@@ -664,11 +664,11 @@ class expected {
     requires is_constructible_v<_Err, const _Err&>
   _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const& {
     using _Up = remove_cvref_t<invoke_result_t<_Func, const _Tp&>>;
-    static_assert(__is_std_expected<_Up>::value, "The result of f(value()) must be a specialization of std::expected");
+    static_assert(__is_std_expected<_Up>::value, "The result of f(**this) must be a specialization of std::expected");
     static_assert(is_same_v<typename _Up::error_type, _Err>,
-                  "The result of f(value()) must have the same error_type as this expected");
+                  "The result of f(**this) must have the same error_type as this expected");
     if (has_value()) {
-      return std::invoke(std::forward<_Func>(__f), value());
+      return std::invoke(std::forward<_Func>(__f), __union_.__val_);
     }
     return _Up(unexpect, error());
   }
@@ -678,11 +678,11 @@ class expected {
   _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) && {
     using _Up = remove_cvref_t<invoke_result_t<_Func, _Tp&&>>;
     static_assert(
-        __is_std_expected<_Up>::value, "The result of f(std::move(value())) must be a specialization of std::expected");
+        __is_std_expected<_Up>::value, "The result of f(std::move(**this)) must be a specialization of std::expected");
     static_assert(is_same_v<typename _Up::error_type, _Err>,
-                  "The result of f(std::move(value())) must have the same error_type as this expected");
+                  "The result of f(std::move(**this)) must have the same error_type as this expected");
     if (has_value()) {
-      return std::invoke(std::forward<_Func>(__f), std::move(value()));
+      return std::invoke(std::forward<_Func>(__f), std::move(__union_.__val_));
     }
     return _Up(unexpect, std::move(error()));
   }
@@ -692,11 +692,11 @@ class expected {
   _LIBCPP_HIDE_FROM_ABI constexpr auto and_then(_Func&& __f) const&& {
     using _Up = remove_cvref_t<invoke_result_t<_Func, const _Tp&&>>;
     static_assert(
-        __is_std_expected<_Up>::value, "The result of f(std::move(value())) must be a specialization of std::expected");
+        __is_std_expected<_Up>::value, "The result of f(std::move(**this)) must be a specialization of std::expected");
     static_assert(is_same_v<typename _Up::error_type, _Err>,
-                  "The result of f(std::move(value())) must have the same error_type as this expected");
+                  "The result of f(std::move(**this)) must have the same error_type as this expected");
     if (has_value()) {
-      return std::invoke(std::forward<_Func>(__f), std::move(value()));
+      return std::invoke(std::forward<_Func>(__f), std::move(__union_.__val_));
     }
     return _Up(unexpect, std::move(error()));
   }
@@ -709,7 +709,7 @@ class expected {
     static_assert(is_same_v<typename _Gp::value_type, _Tp>,
                   "The result of f(error()) must have the same value_type as this expected");
     if (has_value()) {
-      return _Gp(in_place, value());
+      return _Gp(in_place, __union_.__val_);
     }
     return std::invoke(std::forward<_Func>(__f), error());
   }
@@ -722,7 +722,7 @@ class expected {
     static_assert(is_same_v<typename _Gp::value_type, _Tp>,
                   "The result of f(error()) must have the same value_type as this expected");
     if (has_value()) {
-      return _Gp(in_place, value());
+      return _Gp(in_place, __union_.__val_);
     }
     return std::invoke(std::forward<_Func>(__f), error());
   }
@@ -736,7 +736,7 @@ class expected {
     static_assert(is_same_v<typename _Gp::value_type, _Tp>,
                   "The result of f(std::move(error())) must have the same value_type as this expected");
     if (has_value()) {
-      return _Gp(in_place, std::move(value()));
+      return _Gp(in_place, std::move(__union_.__val_));
     }
     return std::invoke(std::forward<_Func>(__f), std::move(error()));
   }
@@ -750,7 +750,7 @@ class expected {
     static_assert(is_same_v<typename _Gp::value_type, _Tp>,
                   "The result of f(std::move(error())) must have the same value_type as this expected");
     if (has_value()) {
-      return _Gp(in_place, std::move(value()));
+      return _Gp(in_place, std::move(__union_.__val_));
     }
     return std::invoke(std::forward<_Func>(__f), std::move(error()));
   }
@@ -763,9 +763,9 @@ class expected {
       return expected<_Up, _Err>(unexpect, error());
     }
     if constexpr (!is_void_v<_Up>) {
-      return expected<_Up, _Err>(__expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), value());
+      return expected<_Up, _Err>(__expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), __union_.__val_);
     } else {
-      std::invoke(std::forward<_Func>(__f), value());
+      std::invoke(std::forward<_Func>(__f), __union_.__val_);
       return expected<_Up, _Err>();
     }
   }
@@ -778,9 +778,9 @@ class expected {
       return expected<_Up, _Err>(unexpect, error());
     }
     if constexpr (!is_void_v<_Up>) {
-      return expected<_Up, _Err>(__expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), value());
+      return expected<_Up, _Err>(__expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), __union_.__val_);
     } else {
-      std::invoke(std::forward<_Func>(__f), value());
+      std::invoke(std::forward<_Func>(__f), __union_.__val_);
       return expected<_Up, _Err>();
     }
   }
@@ -794,9 +794,9 @@ class expected {
     }
     if constexpr (!is_void_v<_Up>) {
       return expected<_Up, _Err>(
-          __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), std::move(value()));
+          __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), std::move(__union_.__val_));
     } else {
-      std::invoke(std::forward<_Func>(__f), std::move(value()));
+      std::invoke(std::forward<_Func>(__f), std::move(__union_.__val_));
       return expected<_Up, _Err>();
     }
   }
@@ -810,9 +810,9 @@ class expected {
     }
     if constexpr (!is_void_v<_Up>) {
       return expected<_Up, _Err>(
-          __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), std::move(value()));
+          __expected_construct_in_place_from_invoke_tag{}, std::forward<_Func>(__f), std::move(__union_.__val_));
     } else {
-      std::invoke(std::forward<_Func>(__f), std::move(value()));
+      std::invoke(std::forward<_Func>(__f), std::move(__union_.__val_));
       return expected<_Up, _Err>();
     }
   }
@@ -824,7 +824,7 @@ class expected {
     static_assert(__valid_std_unexpected<_Gp>::value,
                   "The result of f(error()) must be a valid template argument for unexpected");
     if (has_value()) {
-      return expected<_Tp, _Gp>(in_place, value());
+      return expected<_Tp, _Gp>(in_place, __union_.__val_);
     }
     return expected<_Tp, _Gp>(__expected_construct_unexpected_from_invoke_tag{}, std::forward<_Func>(__f), error());
   }
@@ -836,7 +836,7 @@ class expected {
     static_assert(__valid_std_unexpected<_Gp>::value,
                   "The result of f(error()) must be a valid template argument for unexpected");
     if (has_value()) {
-      return expected<_Tp, _Gp>(in_place, value());
+      return expected<_Tp, _Gp>(in_place, __union_.__val_);
     }
     return expected<_Tp, _Gp>(__expected_construct_unexpected_from_invoke_tag{}, std::forward<_Func>(__f), error());
   }
@@ -848,7 +848,7 @@ class expected {
     static_assert(__valid_std_unexpected<_Gp>::value,
                   "The result of f(std::move(error())) must be a valid template argument for unexpected");
     if (has_value()) {
-      return expected<_Tp, _Gp>(in_place, std::move(value()));
+      return expected<_Tp, _Gp>(in_place, std::move(__union_.__val_));
     }
     return expected<_Tp, _Gp>(
         __expected_construct_unexpected_from_invoke_tag{}, std::forward<_Func>(__f), std::move(error()));
@@ -861,7 +861,7 @@ class expected {
     static_assert(__valid_std_unexpected<_Gp>::value,
                   "The result of f(std::move(error())) must be a valid template argument for unexpected");
     if (has_value()) {
-      return expected<_Tp, _Gp>(in_place, std::move(value()));
+      return expected<_Tp, _Gp>(in_place, std::move(__union_.__val_));
     }
     return expected<_Tp, _Gp>(
         __expected_construct_unexpected_from_invoke_tag{}, std::forward<_Func>(__f), std::move(error()));

diff  --git a/libcxx/test/libcxx/utilities/expected/expected.expected/and_then.mandates.verify.cpp b/libcxx/test/libcxx/utilities/expected/expected.expected/and_then.mandates.verify.cpp
index 692ac60d073831..bedd2c8326ae61 100644
--- a/libcxx/test/libcxx/utilities/expected/expected.expected/and_then.mandates.verify.cpp
+++ b/libcxx/test/libcxx/utilities/expected/expected.expected/and_then.mandates.verify.cpp
@@ -11,22 +11,22 @@
 // Test the mandates
 // template<class F> constexpr auto and_then(F&& f) &;
 // Mandates:
-//  Let U be std::remove_cvref_t<std::invoke_result<F, decltype(value())>>
+//  Let U be std::remove_cvref_t<std::invoke_result<F, decltype(**this)>>
 //  U is a specialization of std::expected and std::is_same_v<U:error_type, E> is true
 
 // template<class F> constexpr auto and_then(F&& f) const &;
 // Mandates:
-//  Let U be std::remove_cvref_t<std::invoke_result<F, decltype(value())>>
+//  Let U be std::remove_cvref_t<std::invoke_result<F, decltype(**this)>>
 //  U is a specialization of std::expected and std::is_same_v<U:error_type, E> is true
 
 // template<class F> constexpr auto and_then(F&& f) &&;
 // Mandates:
-//  Let U be std::remove_cvref_t<std::invoke_result<F, decltype(value())>>
+//  Let U be std::remove_cvref_t<std::invoke_result<F, decltype(**this)>>
 //  U is a specialization of std::expected and std::is_same_v<U:error_type, E> is true
 
 // template<class F> constexpr auto and_then(F&& f) const &&;
 // Mandates:
-//  Let U be std::remove_cvref_t<std::invoke_result<F, decltype(value())>>
+//  Let U be std::remove_cvref_t<std::invoke_result<F, decltype(**this)>>
 //  U is a specialization of std::expected and std::is_same_v<U:error_type, E> is true
 
 #include <expected>
@@ -52,7 +52,7 @@ void test() {
     {
       std::expected<int, int> f1(1);
       f1.and_then(lval_return_not_std_expected); // expected-note{{in instantiation of function template specialization 'std::expected<int, int>::and_then<int (&)(int &)>' requested here}}
-      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(value()) must be a specialization of std::expected}}
+      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(**this) must be a specialization of std::expected}}
       // expected-error-re@*:* {{{{.*}}cannot be used prior to '::' because it has no members}}
       // expected-error-re@*:* {{no matching constructor for initialization of{{.*}}}}
     }
@@ -61,7 +61,7 @@ void test() {
     {
       std::expected<int, int> f1(1);
       f1.and_then(lval_error_type_not_same_as_int);  // expected-note{{in instantiation of function template specialization 'std::expected<int, int>::and_then<std::expected<int, NotSameAsInt> (&)(int &)>' requested here}}
-      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(value()) must have the same error_type as this expected}}
+      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(**this) must have the same error_type as this expected}}
     }
   }
 
@@ -71,7 +71,7 @@ void test() {
     {
       const std::expected<int, int> f1(1);
       f1.and_then(clval_return_not_std_expected); // expected-note{{in instantiation of function template specialization 'std::expected<int, int>::and_then<int (&)(const int &)>' requested here}}
-      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(value()) must be a specialization of std::expected}}
+      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(**this) must be a specialization of std::expected}}
       // expected-error-re@*:* {{{{.*}}cannot be used prior to '::' because it has no members}}
       // expected-error-re@*:* {{no matching constructor for initialization of{{.*}}}}
     }
@@ -80,7 +80,7 @@ void test() {
     {
       const std::expected<int, int> f1(1);
       f1.and_then(clval_error_type_not_same_as_int);  // expected-note{{in instantiation of function template specialization 'std::expected<int, int>::and_then<std::expected<int, NotSameAsInt> (&)(const int &)>' requested here}}
-      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(value()) must have the same error_type as this expected}}
+      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(**this) must have the same error_type as this expected}}
 
     }
   }
@@ -91,7 +91,7 @@ void test() {
     {
       std::expected<int, int> f1(1);
       std::move(f1).and_then(rval_return_not_std_expected); // expected-note{{in instantiation of function template specialization 'std::expected<int, int>::and_then<int (&)(int &&)>' requested here}}
-      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(std::move(value())) must be a specialization of std::expected}}
+      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(std::move(**this)) must be a specialization of std::expected}}
       // expected-error-re@*:* {{{{.*}}cannot be used prior to '::' because it has no members}}
       // expected-error-re@*:* {{no matching constructor for initialization of{{.*}}}}
     }
@@ -100,7 +100,7 @@ void test() {
     {
       std::expected<int, int> f1(1);
       std::move(f1).and_then(rval_error_type_not_same_as_int); // expected-note{{in instantiation of function template specialization 'std::expected<int, int>::and_then<std::expected<int, NotSameAsInt> (&)(int &&)>' requested here}}
-      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(std::move(value())) must have the same error_type as this expected}}
+      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(std::move(**this)) must have the same error_type as this expected}}
     }
   }
 
@@ -110,7 +110,7 @@ void test() {
     {
       const std::expected<int, int> f1(1);
       std::move(f1).and_then(crval_return_not_std_expected); // expected-note{{in instantiation of function template specialization 'std::expected<int, int>::and_then<int (&)(const int &&)>' requested here}}
-      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(std::move(value())) must be a specialization of std::expected}}
+      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(std::move(**this)) must be a specialization of std::expected}}
       // expected-error-re@*:* {{{{.*}}cannot be used prior to '::' because it has no members}}
       // expected-error-re@*:* {{no matching constructor for initialization of{{.*}}}}
     }
@@ -119,7 +119,7 @@ void test() {
     {
       const std::expected<int, int> f1(1);
       std::move(f1).and_then(crval_error_type_not_same_as_int); // expected-note{{in instantiation of function template specialization 'std::expected<int, int>::and_then<std::expected<int, NotSameAsInt> (&)(const int &&)>' requested here}}
-      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(std::move(value())) must have the same error_type as this expected}}
+      // expected-error-re@*:* {{{{(static_assert|static assertion)}} failed {{.*}}The result of f(std::move(**this)) must have the same error_type as this expected}}
     }
   }
 }

diff  --git a/libcxx/test/std/utilities/expected/expected.expected/monadic/and_then.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/monadic/and_then.pass.cpp
index 108644e200b30a..293c2d39d4a325 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/monadic/and_then.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/monadic/and_then.pass.cpp
@@ -22,6 +22,8 @@
 #include <type_traits>
 #include <utility>
 
+#include "../../types.h"
+
 struct LVal {
   constexpr std::expected<int, int> operator()(int&) { return 1; }
   std::expected<int, int> operator()(const int&)  = delete;
@@ -153,6 +155,7 @@ concept has_and_then = requires(E&& e, F&& f) {
   {std::forward<E>(e).and_then(std::forward<F>(f))};
 };
 
+// clang-format off
 static_assert( has_and_then<std::expected<int, int>&, std::expected<int, int>(int&)>);
 static_assert(!has_and_then<std::expected<int, NonCopyable>&, std::expected<int, NonCopyable>(int&)>);
 static_assert( has_and_then<const std::expected<int, int>&, std::expected<int, int>(const int&)>);
@@ -166,7 +169,11 @@ static_assert(!has_and_then<const std::expected<int, NonMovable>&&, std::expecte
 static_assert(!has_and_then<const std::expected<int, std::unique_ptr<int>>&, int()>);
 static_assert(!has_and_then<const std::expected<int, std::unique_ptr<int>>&&, int()>);
 
-// clang-format off
+// [LWG 3983] https://cplusplus.github.io/LWG/issue3938, check std::expected monadic ops well-formed with move-only error_type.
+// There are no effects for `&` and `const &` overload, because the constraints requires is_constructible_v<E, decltype(error())> is true.
+static_assert(has_and_then<std::expected<int, MoveOnlyErrorType>&&, std::expected<int, MoveOnlyErrorType>(int)>);
+static_assert(has_and_then<const std::expected<int, MoveOnlyErrorType>&&, std::expected<int, MoveOnlyErrorType>(const int)>);
+
 constexpr void test_val_types() {
   // Test & overload
   {
@@ -260,9 +267,26 @@ constexpr void test_sfinae() {
   std::move(e).and_then(l);
 }
 
+constexpr void test_move_only_error_type() {
+  // Test &&
+  {
+    std::expected<int, MoveOnlyErrorType> e;
+    auto l = [](int) { return std::expected<int, MoveOnlyErrorType>{}; };
+    std::move(e).and_then(l);
+  }
+
+  // Test const&&
+  {
+    const std::expected<int, MoveOnlyErrorType> e;
+    auto l = [](const int) { return std::expected<int, MoveOnlyErrorType>{}; };
+    std::move(e).and_then(l);
+  }
+}
+
 constexpr bool test() {
   test_sfinae();
   test_val_types();
+  test_move_only_error_type();
 
   std::expected<int, int> e(std::unexpected<int>(1));
   const auto& ce = e;

diff  --git a/libcxx/test/std/utilities/expected/expected.expected/monadic/or_else.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/monadic/or_else.pass.cpp
index bf5b481a158ac6..b472a2756be9f9 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/monadic/or_else.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/monadic/or_else.pass.cpp
@@ -22,6 +22,8 @@
 #include <type_traits>
 #include <utility>
 
+#include "../../types.h"
+
 struct LVal {
   constexpr std::expected<int, int> operator()(int&) { return 1; }
   std::expected<int, int> operator()(const int&)  = delete;
@@ -83,12 +85,17 @@ concept has_or_else =
     requires(E&& e, F&& f) {
       { std::forward<E>(e).or_else(std::forward<F>(f)) };
     };
-
+// clang-format off
 // [LWG 3877] https://cplusplus.github.io/LWG/issue3877, check constraint failing but not compile error inside the function body.
 static_assert(!has_or_else<const std::expected<std::unique_ptr<int>, int>&, int()>);
 static_assert(!has_or_else<const std::expected<std::unique_ptr<int>, int>&&, int()>);
 
-// clang-format off
+// [LWG 3983] https://cplusplus.github.io/LWG/issue3938, check std::expected monadic ops well-formed with move-only error_type.
+static_assert(has_or_else<std::expected<int, MoveOnlyErrorType>&, std::expected<int, int>(MoveOnlyErrorType &)>);
+static_assert(has_or_else<const std::expected<int, MoveOnlyErrorType>&, std::expected<int, int>(const MoveOnlyErrorType &)>);
+static_assert(has_or_else<std::expected<int, MoveOnlyErrorType>&&, std::expected<int, int>(MoveOnlyErrorType&&)>);
+static_assert(has_or_else<const std::expected<int, MoveOnlyErrorType>&&, std::expected<int, int>(const MoveOnlyErrorType&&)>);
+
 constexpr void test_val_types() {
   // Test & overload
   {
@@ -175,9 +182,40 @@ constexpr void test_sfinae() {
   std::move(e).or_else(l);
 }
 
+constexpr void test_move_only_error_type() {
+  // Test &
+  {
+      std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](MoveOnlyErrorType&) { return std::expected<int, int>{}; };
+      e.or_else(l);
+  }
+
+  // Test const&
+  {
+      const std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](const MoveOnlyErrorType&) { return std::expected<int, int>{}; };
+      e.or_else(l);
+  }
+
+  // Test &&
+  {
+      std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](MoveOnlyErrorType&&) { return std::expected<int, int>{}; };
+      std::move(e).or_else(l);
+  }
+
+  // Test const&&
+  {
+      const std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](const MoveOnlyErrorType&&) { return std::expected<int, int>{}; };
+      std::move(e).or_else(l);
+  }
+}
+
 constexpr bool test() {
   test_sfinae();
   test_val_types();
+  test_move_only_error_type();
 
   std::expected<int, int> e(1);
   const auto& ce = e;

diff  --git a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
index 41908c76ab6180..f443613fe7f343 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform.pass.cpp
@@ -26,6 +26,8 @@
 #include <type_traits>
 #include <utility>
 
+#include "../../types.h"
+
 struct LVal {
   constexpr int operator()(int&) { return 1; }
   int operator()(const int&)  = delete;
@@ -98,11 +100,16 @@ concept has_transform =
       { std::forward<E>(e).transform(std::forward<F>(f)) };
     };
 
+// clang-format off
 // [LWG 3877] https://cplusplus.github.io/LWG/issue3877, check constraint failing but not compile error inside the function body.
 static_assert(!has_transform<const std::expected<int, std::unique_ptr<int>>&, int()>);
 static_assert(!has_transform<const std::expected<int, std::unique_ptr<int>>&&, int()>);
 
-// clang-format off
+// [LWG 3983] https://cplusplus.github.io/LWG/issue3938, check std::expected monadic ops well-formed with move-only error_type.
+// There are no effects for `&` and `const &` overload, because the constraints requires is_constructible_v<E, decltype(error())> is true.
+static_assert(has_transform<std::expected<int, MoveOnlyErrorType>&&, int(int)>);
+static_assert(has_transform<const std::expected<int, MoveOnlyErrorType>&&, int(const int)>);
+
 constexpr void test_val_types() {
   // Test & overload
   {
@@ -236,10 +243,27 @@ constexpr void test_sfinae() {
   std::move(ce1).transform(never_called);
 }
 
+constexpr void test_move_only_error_type() {
+  // Test &&
+  {
+      std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](int) { return 0; };
+      std::move(e).transform(l);
+  }
+
+  // Test const&&
+  {
+      const std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](const int) { return 0; };
+      std::move(e).transform(l);
+  }
+}
+
 constexpr bool test() {
   test_sfinae();
   test_val_types();
   test_direct_non_list_init();
+  test_move_only_error_type();
   return true;
 }
 

diff  --git a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
index edcc56abc2f7d0..84b57aea3cd231 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/monadic/transform_error.pass.cpp
@@ -26,6 +26,8 @@
 #include <type_traits>
 #include <utility>
 
+#include "../../types.h"
+
 struct LVal {
   constexpr int operator()(int&) { return 1; }
   int operator()(const int&)  = delete;
@@ -98,11 +100,17 @@ concept has_transform_error =
       { std::forward<E>(e).transform_error(std::forward<F>(f)) };
     };
 
+// clang-format off
 // [LWG 3877] https://cplusplus.github.io/LWG/issue3877, check constraint failing but not compile error inside the function body.
 static_assert(!has_transform_error<const std::expected<std::unique_ptr<int>, int>&, int()>);
 static_assert(!has_transform_error<const std::expected<std::unique_ptr<int>, int>&&, int()>);
 
-// clang-format off
+// [LWG 3983] https://cplusplus.github.io/LWG/issue3938, check std::expected monadic ops well-formed with move-only error_type.
+static_assert(has_transform_error<std::expected<int, MoveOnlyErrorType>&, int(MoveOnlyErrorType &)>);
+static_assert(has_transform_error<const std::expected<int, MoveOnlyErrorType>&, int(const MoveOnlyErrorType &)>);
+static_assert(has_transform_error<std::expected<int, MoveOnlyErrorType>&&, int(MoveOnlyErrorType&&)>);
+static_assert(has_transform_error<const std::expected<int, MoveOnlyErrorType>&&, int(const MoveOnlyErrorType&&)>);
+
 constexpr void test_val_types() {
   // Test & overload
   {
@@ -206,10 +214,42 @@ constexpr void test_sfinae() {
   std::move(ce1).transform_error(never_called);
 }
 
+constexpr void test_move_only_error_type() {
+  // Test &
+  {
+      std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](MoveOnlyErrorType&) { return 0; };
+      e.transform_error(l);
+  }
+
+  // Test const&
+  {
+      const std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](const MoveOnlyErrorType&) { return 0; };
+      e.transform_error(l);
+  }
+
+  // Test &&
+  {
+      std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](MoveOnlyErrorType&&) { return 0; };
+      std::move(e).transform_error(l);
+  }
+
+  // Test const&&
+  {
+      const std::expected<int, MoveOnlyErrorType> e;
+      auto l = [](const MoveOnlyErrorType&&) { return 0; };
+      std::move(e).transform_error(l);
+  }
+}
+
 constexpr bool test() {
   test_sfinae();
   test_val_types();
   test_direct_non_list_init();
+  test_move_only_error_type();
+
   return true;
 }
 

diff  --git a/libcxx/test/std/utilities/expected/types.h b/libcxx/test/std/utilities/expected/types.h
index 278ab0f0ec7468..7c7e517785b4f7 100644
--- a/libcxx/test/std/utilities/expected/types.h
+++ b/libcxx/test/std/utilities/expected/types.h
@@ -142,4 +142,12 @@ struct ThrowOnMove {
 
 #endif // TEST_HAS_NO_EXCEPTIONS
 
+struct MoveOnlyErrorType {
+  constexpr MoveOnlyErrorType(int) {}
+  MoveOnlyErrorType(MoveOnlyErrorType&&) {}
+  MoveOnlyErrorType(const MoveOnlyErrorType&&) {}
+  MoveOnlyErrorType(const MoveOnlyErrorType&)            = delete;
+  MoveOnlyErrorType& operator=(const MoveOnlyErrorType&) = delete;
+};
+
 #endif // TEST_STD_UTILITIES_EXPECTED_TYPES_H


        


More information about the libcxx-commits mailing list