This is the subject of two NB comments on C++26 which seem likely to be
approved. We're allowed to make this change as QoI anyway, even if it
isn't approved for the standard, and it should apply to C++23 as well to
avoid ABI changes between C++23 and C++26.

libstdc++-v3/ChangeLog:

        * include/std/expected (__expected::__trivially_replaceable)
        (__expected::__usable_for_assign)
        (__expected::__usable_for_trivial_assign)
        (__expected::__can_flip_state): New concepts.
        (expected::operator=): Adjust constraints
        on existing overloads and add defaulted overload.
        (expected<cv void, E>::operator=): Likewise.
        * testsuite/20_util/expected/requirements.cc: Check for trivial
        and nothrow assignments.
---

Tested x86_64-linux.

 libstdc++-v3/include/std/expected             | 87 ++++++++++++++++---
 .../20_util/expected/requirements.cc          | 77 ++++++++++------
 2 files changed, 127 insertions(+), 37 deletions(-)

diff --git a/libstdc++-v3/include/std/expected 
b/libstdc++-v3/include/std/expected
index 948c2cbe6085..a41a8f2572dd 100644
--- a/libstdc++-v3/include/std/expected
+++ b/libstdc++-v3/include/std/expected
@@ -340,6 +340,27 @@ namespace __expected
     concept __not_constructing_bool_from_expected
       = ! is_same_v<remove_cv_t<_Tp>, bool>
          || ! __is_expected<remove_cvref_t<_Up>>;
+
+  template<typename _Tp, typename _Up = remove_cvref_t<_Tp>>
+    concept __trivially_replaceable
+      = is_trivially_constructible_v<_Up, _Tp>
+         && is_trivially_assignable_v<_Up&, _Tp>
+         && is_trivially_destructible_v<_Up>;
+
+  template<typename _Tp, typename _Up = remove_cvref_t<_Tp>>
+    concept __usable_for_assign
+      = is_constructible_v<_Up, _Tp> && is_assignable_v<_Up&, _Tp>;
+
+  template<typename _Tp>
+    concept __usable_for_trivial_assign
+      = __trivially_replaceable<_Tp> && __usable_for_assign<_Tp>;
+
+  // For copy/move assignment to replace T with E, or vice versa,
+  // we require at least one of them to be nothrow move constructible.
+  template<typename _Tp, typename _Er>
+    concept __can_flip_state
+      = is_nothrow_move_constructible_v<_Tp>
+         || is_nothrow_move_constructible_v<_Er>;
 }
 /// @endcond
 
@@ -560,18 +581,31 @@ namespace __expected
 
       // assignment
 
+      // Deleted copy assignment, when constraints not met for other overloads
       expected& operator=(const expected&) = delete;
 
+      // Trivial copy assignment
+      expected&
+      operator=(const expected&)
+      noexcept(__and_v<is_nothrow_copy_constructible<_Tp>,
+                      is_nothrow_copy_constructible<_Er>,
+                      is_nothrow_copy_assignable<_Tp>,
+                      is_nothrow_copy_assignable<_Er>>)
+      requires __expected::__usable_for_trivial_assign<const _Tp&>
+           && __expected::__usable_for_trivial_assign<const _Er&>
+           && __expected::__can_flip_state<_Tp, _Er>
+       = default;
+
+      // Non-trivial copy assignment
       constexpr expected&
       operator=(const expected& __x)
       noexcept(__and_v<is_nothrow_copy_constructible<_Tp>,
                       is_nothrow_copy_constructible<_Er>,
                       is_nothrow_copy_assignable<_Tp>,
                       is_nothrow_copy_assignable<_Er>>)
-      requires is_copy_assignable_v<_Tp> && is_copy_constructible_v<_Tp>
-           && is_copy_assignable_v<_Er> && is_copy_constructible_v<_Er>
-           && (is_nothrow_move_constructible_v<_Tp>
-               || is_nothrow_move_constructible_v<_Er>)
+      requires __expected::__usable_for_assign<const _Tp&>
+           && __expected::__usable_for_assign<const _Er&>
+           && __expected::__can_flip_state<_Tp, _Er>
       {
        if (__x._M_has_value)
          this->_M_assign_val(__x._M_val);
@@ -580,16 +614,28 @@ namespace __expected
        return *this;
       }
 
+      // Trivial move assignment
+      expected&
+      operator=(expected&&)
+      noexcept(__and_v<is_nothrow_move_constructible<_Tp>,
+                      is_nothrow_move_constructible<_Er>,
+                      is_nothrow_move_assignable<_Tp>,
+                      is_nothrow_move_assignable<_Er>>)
+      requires __expected::__usable_for_trivial_assign<_Tp&&>
+           && __expected::__usable_for_trivial_assign<_Er&&>
+           && __expected::__can_flip_state<_Tp, _Er>
+       = default;
+
+      // Non-trivial move assignment
       constexpr expected&
       operator=(expected&& __x)
       noexcept(__and_v<is_nothrow_move_constructible<_Tp>,
                       is_nothrow_move_constructible<_Er>,
                       is_nothrow_move_assignable<_Tp>,
                       is_nothrow_move_assignable<_Er>>)
-      requires is_move_assignable_v<_Tp> && is_move_constructible_v<_Tp>
-           && is_move_assignable_v<_Er> && is_move_constructible_v<_Er>
-           && (is_nothrow_move_constructible_v<_Tp>
-               || is_nothrow_move_constructible_v<_Er>)
+      requires __expected::__usable_for_assign<_Tp&&>
+           && __expected::__usable_for_assign<_Er&&>
+           && __expected::__can_flip_state<_Tp, _Er>
       {
        if (__x._M_has_value)
          _M_assign_val(std::move(__x._M_val));
@@ -1447,14 +1493,23 @@ namespace __expected
 
       // assignment
 
+      // Deleted copy assignment, when constraints not met for other overloads
       expected& operator=(const expected&) = delete;
 
+      // Trivial copy assignment
+      expected&
+      operator=(const expected&)
+      noexcept(__and_v<is_nothrow_copy_constructible<_Er>,
+                      is_nothrow_copy_assignable<_Er>>)
+      requires __expected::__usable_for_trivial_assign<const _Er&>
+       = default;
+
+      // Non-trivial copy assignment
       constexpr expected&
       operator=(const expected& __x)
       noexcept(__and_v<is_nothrow_copy_constructible<_Er>,
                       is_nothrow_copy_assignable<_Er>>)
-      requires is_copy_constructible_v<_Er>
-           && is_copy_assignable_v<_Er>
+      requires __expected::__usable_for_assign<const _Er&>
       {
        if (__x._M_has_value)
          emplace();
@@ -1463,12 +1518,20 @@ namespace __expected
        return *this;
       }
 
+      // Trivial move assignment
+      expected&
+      operator=(expected&&)
+      noexcept(__and_v<is_nothrow_move_constructible<_Er>,
+                      is_nothrow_move_assignable<_Er>>)
+      requires __expected::__usable_for_trivial_assign<_Er&&>
+       = default;
+
+      // Non-trivial move assignment
       constexpr expected&
       operator=(expected&& __x)
       noexcept(__and_v<is_nothrow_move_constructible<_Er>,
                       is_nothrow_move_assignable<_Er>>)
-      requires is_move_constructible_v<_Er>
-           && is_move_assignable_v<_Er>
+      requires __expected::__usable_for_assign<_Er&&>
       {
        if (__x._M_has_value)
          emplace();
diff --git a/libstdc++-v3/testsuite/20_util/expected/requirements.cc 
b/libstdc++-v3/testsuite/20_util/expected/requirements.cc
index c7ef5b603bf7..d807724b5470 100644
--- a/libstdc++-v3/testsuite/20_util/expected/requirements.cc
+++ b/libstdc++-v3/testsuite/20_util/expected/requirements.cc
@@ -86,39 +86,66 @@ static_assert( move_constructible< void, E   > == NoThrow );
 // Copy assignment
 
 template<typename T, typename E>
-  constexpr bool copy_assignable
-    = std::is_copy_assignable_v<std::expected<T, E>>;
+  constexpr Result copy_assignable
+    = std::is_trivially_copy_assignable_v<std::expected<T, E>> ? Trivial
+      : std::is_nothrow_copy_assignable_v<std::expected<T, E>> ? NoThrow
+      : std::is_copy_assignable_v<std::expected<T, E>> ? Yes
+      : No;
 
 struct F { F(F&&); F& operator=(const F&); }; // not copy-constructible
-struct G { G(const G&); G(G&&); G& operator=(const G&); }; // throwing move
 
-static_assert( copy_assignable< int,  int > );
-static_assert( copy_assignable< F,    int > == false );
-static_assert( copy_assignable< int,  F   > == false );
-static_assert( copy_assignable< F,    F   > == false );
-static_assert( copy_assignable< G,    int > );
-static_assert( copy_assignable< int,  G   > );
-static_assert( copy_assignable< G,    G   > == false );
-static_assert( copy_assignable< void, int > );
-static_assert( copy_assignable< void, F > == false );
-static_assert( copy_assignable< void, G > );
+template<bool CopyCtor, bool MoveCtor, bool CopyAssign, bool MoveAssign>
+struct X {
+    X(const X&) noexcept(CopyCtor);
+    X(X&&) noexcept(MoveCtor);
+    X& operator=(const X&) noexcept(CopyAssign);
+    X& operator=(X&&) noexcept(MoveAssign);
+};
+using G = X<false, false, false, false>;
+using H = X<false, true, true, true>;
+using I = X<true, true, true, false>;
+
+static_assert( copy_assignable< int,  int > == Trivial );
+static_assert( copy_assignable< F,    int > == No );
+static_assert( copy_assignable< int,  F   > == No );
+static_assert( copy_assignable< F,    F   > == No );
+static_assert( copy_assignable< G,    int > == Yes );
+static_assert( copy_assignable< int,  G   > == Yes );
+static_assert( copy_assignable< G,    G   > == No );
+static_assert( copy_assignable< int,  H   > == Yes );
+static_assert( copy_assignable< H,    H   > == Yes );
+static_assert( copy_assignable< int,  I   > == NoThrow );
+static_assert( copy_assignable< I,    I   > == NoThrow );
+static_assert( copy_assignable< void, int > == Trivial );
+static_assert( copy_assignable< void, F > == No );
+static_assert( copy_assignable< void, G > == Yes );
+static_assert( copy_assignable< void, H > == Yes );
+static_assert( copy_assignable< void, I > == NoThrow );
 
 // Move assignment
 
 template<typename T, typename E>
-  constexpr bool move_assignable
-    = std::is_move_assignable_v<std::expected<T, E>>;
+  constexpr Result move_assignable
+    = std::is_trivially_move_assignable_v<std::expected<T, E>> ? Trivial
+      : std::is_nothrow_move_assignable_v<std::expected<T, E>> ? NoThrow
+      : std::is_move_assignable_v<std::expected<T, E>> ? Yes
+      : No;
 
-static_assert( move_assignable< int,  int > );
-static_assert( move_assignable< F,    int > );
-static_assert( move_assignable< int,  F   > );
-static_assert( move_assignable< F,    F   > == false );
-static_assert( move_assignable< G,    int > );
-static_assert( move_assignable< int,  G   > );
-static_assert( move_assignable< G,    G   > == false );
-static_assert( move_assignable< void, int > );
-static_assert( move_assignable< void, F > );
-static_assert( move_assignable< void, G > );
+static_assert( move_assignable< int,  int > == Trivial );
+static_assert( move_assignable< F,    int > == Yes );
+static_assert( move_assignable< int,  F   > == Yes );
+static_assert( move_assignable< F,    F   > == No );
+static_assert( move_assignable< G,    int > == Yes );
+static_assert( move_assignable< int,  G   > == Yes );
+static_assert( move_assignable< G,    G   > == No );
+static_assert( move_assignable< int,  H   > == NoThrow );
+static_assert( move_assignable< H,    H   > == NoThrow );
+static_assert( move_assignable< I,    I   > == Yes );
+static_assert( move_assignable< void, int > == Trivial );
+static_assert( move_assignable< void, F > == Yes );
+static_assert( move_assignable< void, G > == Yes );
+static_assert( move_assignable< void, H > == NoThrow );
+static_assert( move_assignable< void, I > == Yes );
 
 // QoI properties
 static_assert( sizeof(std::expected<char, unsigned char>) == 2 );
-- 
2.52.0

Reply via email to