[v3] PR libstdc++/84601

Message ID CAFk2RUZb44nxj3BgeWTRstdo5x4OVHb8HGK0_irK=MDq0zaWUg@mail.gmail.com
State New
Headers show
Series
  • [v3] PR libstdc++/84601
Related show

Commit Message

Ville Voutilainen Feb. 28, 2018, 12:27 p.m.
Partially tested on Linux-x64, will run full suite on Linux-PPC64.

2018-02-28  Ville Voutilainen  <ville.voutilainen@gmail.com>

    PR libstdc++/84601
    * include/std/optional (_Optional_payload): Split into multiple
    specializations that can handle different cases of trivial or
    non-trivial assignment operators.
    * testsuite/20_util/optional/84601.cc: New.

Comments

Ville Voutilainen Feb. 28, 2018, 1:12 p.m. | #1
On 28 February 2018 at 14:27, Ville Voutilainen
<ville.voutilainen@gmail.com> wrote:
> Partially tested on Linux-x64, will run full suite on Linux-PPC64.


Which didn't reveal anything I didn't know, but reminded me to adjust
the negative tests. :)

2018-02-28  Ville Voutilainen  <ville.voutilainen@gmail.com>

    PR libstdc++/84601
    * include/std/optional (_Optional_payload): Split into multiple
    specializations that can handle different cases of trivial or
    non-trivial assignment operators.
    * testsuite/20_util/optional/84601.cc: New.
    * testsuite/20_util/optional/cons/value_neg.cc: Adjust.
diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional
index 01fc06b..0aa20dd 100644
--- a/libstdc++-v3/include/std/optional
+++ b/libstdc++-v3/include/std/optional
@@ -98,13 +98,137 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   { _GLIBCXX_THROW_OR_ABORT(bad_optional_access()); }
 
 
-  // Payload for constexpr optionals.
+  // Payload for optionals with non-trivial destructor.
   template <typename _Tp,
 	    bool /*_HasTrivialDestructor*/ =
-	      is_trivially_destructible<_Tp>::value>
+	      is_trivially_destructible<_Tp>::value,
+	    bool /*_HasTrivialCopyAssignment*/ =
+	      is_trivially_copy_assignable<_Tp>::value,
+	    bool /*_HasTrivialMoveAssignment*/ =
+	      is_trivially_move_assignable<_Tp>::value>
     struct _Optional_payload
     {
       constexpr _Optional_payload()
+	: _M_empty() {}
+
+      template <typename... _Args>
+      constexpr _Optional_payload(in_place_t, _Args&&... __args)
+	: _M_payload(std::forward<_Args>(__args)...),
+	  _M_engaged(true) {}
+
+      template<typename _Up, typename... _Args>
+      constexpr _Optional_payload(std::initializer_list<_Up> __il,
+				  _Args&&... __args)
+	: _M_payload(__il, std::forward<_Args>(__args)...),
+	  _M_engaged(true) {}
+      constexpr
+      _Optional_payload(bool __engaged, const _Optional_payload& __other)
+	: _Optional_payload(__other)
+      {}
+
+      constexpr
+      _Optional_payload(bool __engaged, _Optional_payload&& __other)
+	: _Optional_payload(std::move(__other))
+      {}
+
+      constexpr _Optional_payload(const _Optional_payload& __other)
+      {
+	if (__other._M_engaged)
+	  this->_M_construct(__other._M_payload);
+      }
+
+      constexpr _Optional_payload(_Optional_payload&& __other)
+      {
+	if (__other._M_engaged)
+	  this->_M_construct(std::move(__other._M_payload));
+      }
+
+      _Optional_payload&
+      operator=(const _Optional_payload& __other)
+      {
+        if (this->_M_engaged && __other._M_engaged)
+          this->_M_get() = __other._M_get();
+        else
+	  {
+	    if (__other._M_engaged)
+	      this->_M_construct(__other._M_get());
+	    else
+	      this->_M_reset();
+	  }
+	return *this;
+      }
+
+      _Optional_payload&
+      operator=(_Optional_payload&& __other)
+      noexcept(__and_<is_nothrow_move_constructible<_Tp>,
+		      is_nothrow_move_assignable<_Tp>>())
+      {
+	if (this->_M_engaged && __other._M_engaged)
+	  this->_M_get() = std::move(__other._M_get());
+	else
+	  {
+	    if (__other._M_engaged)
+	      this->_M_construct(std::move(__other._M_get()));
+	    else
+	      this->_M_reset();
+	  }
+	return *this;
+      }
+
+      using _Stored_type = remove_const_t<_Tp>;
+      struct _Empty_byte { };
+      union {
+          _Empty_byte _M_empty;
+          _Stored_type _M_payload;
+      };
+      bool _M_engaged = false;
+
+      ~_Optional_payload()
+      {
+        if (_M_engaged)
+          _M_payload.~_Stored_type();
+      }
+
+      template<typename... _Args>
+        void
+        _M_construct(_Args&&... __args)
+        noexcept(is_nothrow_constructible<_Stored_type, _Args...>())
+        {
+          ::new ((void *) std::__addressof(this->_M_payload))
+            _Stored_type(std::forward<_Args>(__args)...);
+          this->_M_engaged = true;
+        }
+
+      // The _M_get operations have _M_engaged as a precondition.
+      constexpr _Tp&
+	_M_get() noexcept
+      {
+	return this->_M_payload;
+      }
+
+      constexpr const _Tp&
+	_M_get() const noexcept
+      {
+	return this->_M_payload;
+      }
+
+      // _M_reset is a 'safe' operation with no precondition.
+      void
+      _M_reset() noexcept
+      {
+	if (this->_M_engaged)
+	  {
+	    this->_M_engaged = false;
+	    this->_M_payload.~_Stored_type();
+	  }
+      }
+    };
+
+  // Payload for constexpr optionals.
+  template <typename _Tp>
+    struct _Optional_payload<_Tp, true, true, true>
+    {
+      constexpr _Optional_payload()
 	: _M_empty(), _M_engaged(false) {}
 
       template<typename... _Args>
@@ -161,44 +285,294 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       bool _M_engaged;
     };
 
-  // Payload for optionals with non-trivial destructor.
+  // Payload for optionals with non-trivial copy assignment.
   template <typename _Tp>
-    struct _Optional_payload<_Tp, false>
+    struct _Optional_payload<_Tp, true, false, true>
     {
       constexpr _Optional_payload()
-	: _M_empty() {}
+	: _M_empty(), _M_engaged(false) {}
 
-      template <typename... _Args>
+      template<typename... _Args>
       constexpr _Optional_payload(in_place_t, _Args&&... __args)
 	: _M_payload(std::forward<_Args>(__args)...),
+	  _M_engaged(true)
+      {}
+
+      template<typename _Up, typename... _Args>
+      constexpr _Optional_payload(std::initializer_list<_Up> __il,
+				  _Args&&... __args)
+	: _M_payload(__il, std::forward<_Args>(__args)...),
 	  _M_engaged(true) {}
 
+      template <class _Up> struct __ctor_tag {};
+
+      constexpr _Optional_payload(__ctor_tag<bool>,
+				  const _Tp& __other)
+	: _M_payload(__other),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(__ctor_tag<void>)
+	: _M_empty(), _M_engaged(false)
+      {}
+
+      constexpr _Optional_payload(__ctor_tag<bool>, _Tp&& __other)
+	: _M_payload(std::move(__other)),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  const _Optional_payload& __other)
+	: _Optional_payload(__engaged ?
+			    _Optional_payload(__ctor_tag<bool>{},
+					      __other._M_payload) :
+			    _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  _Optional_payload&& __other)
+	: _Optional_payload(__engaged
+			    ? _Optional_payload(__ctor_tag<bool>{},
+						std::move(__other._M_payload))
+			    : _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      _Optional_payload(const _Optional_payload&) = default;
+      _Optional_payload(_Optional_payload&&) = default;
+
+      _Optional_payload&
+      operator=(const _Optional_payload& __other)
+      {
+        if (this->_M_engaged && __other._M_engaged)
+          this->_M_get() = __other._M_get();
+        else
+	  {
+	    if (__other._M_engaged)
+	      this->_M_construct(__other._M_get());
+	    else
+	      this->_M_reset();
+	  }
+	return *this;
+      }
+
+      _Optional_payload&
+      operator=(_Optional_payload&& __other) = default;
+
+      using _Stored_type = remove_const_t<_Tp>;
+      struct _Empty_byte { };
+      union {
+          _Empty_byte _M_empty;
+          _Stored_type _M_payload;
+      };
+      bool _M_engaged;
+
+      template<typename... _Args>
+        void
+        _M_construct(_Args&&... __args)
+        noexcept(is_nothrow_constructible<_Stored_type, _Args...>())
+        {
+          ::new ((void *) std::__addressof(this->_M_payload))
+            _Stored_type(std::forward<_Args>(__args)...);
+          this->_M_engaged = true;
+        }
+
+      // The _M_get operations have _M_engaged as a precondition.
+      constexpr _Tp&
+	_M_get() noexcept
+      {
+	return this->_M_payload;
+      }
+
+      constexpr const _Tp&
+	_M_get() const noexcept
+      {
+	return this->_M_payload;
+      }
+
+      // _M_reset is a 'safe' operation with no precondition.
+      void
+      _M_reset() noexcept
+      {
+	if (this->_M_engaged)
+	  {
+	    this->_M_engaged = false;
+	    this->_M_payload.~_Stored_type();
+	  }
+      }
+    };
+
+  // Payload for optionals with non-trivial move assignment.
+  template <typename _Tp>
+    struct _Optional_payload<_Tp, true, true, false>
+    {
+      constexpr _Optional_payload()
+	: _M_empty(), _M_engaged(false) {}
+
+      template<typename... _Args>
+      constexpr _Optional_payload(in_place_t, _Args&&... __args)
+	: _M_payload(std::forward<_Args>(__args)...),
+	  _M_engaged(true)
+      {}
+
       template<typename _Up, typename... _Args>
       constexpr _Optional_payload(std::initializer_list<_Up> __il,
 				  _Args&&... __args)
 	: _M_payload(__il, std::forward<_Args>(__args)...),
 	  _M_engaged(true) {}
-      constexpr
-      _Optional_payload(bool __engaged, const _Optional_payload& __other)
-	: _Optional_payload(__other)
+
+      template <class _Up> struct __ctor_tag {};
+
+      constexpr _Optional_payload(__ctor_tag<bool>,
+				  const _Tp& __other)
+	: _M_payload(__other),
+	  _M_engaged(true)
       {}
 
-      constexpr
-      _Optional_payload(bool __engaged, _Optional_payload&& __other)
-	: _Optional_payload(std::move(__other))
+      constexpr _Optional_payload(__ctor_tag<void>)
+	: _M_empty(), _M_engaged(false)
       {}
 
-      constexpr _Optional_payload(const _Optional_payload& __other)
+      constexpr _Optional_payload(__ctor_tag<bool>, _Tp&& __other)
+	: _M_payload(std::move(__other)),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  const _Optional_payload& __other)
+	: _Optional_payload(__engaged ?
+			    _Optional_payload(__ctor_tag<bool>{},
+					      __other._M_payload) :
+			    _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  _Optional_payload&& __other)
+	: _Optional_payload(__engaged
+			    ? _Optional_payload(__ctor_tag<bool>{},
+						std::move(__other._M_payload))
+			    : _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      _Optional_payload(const _Optional_payload&) = default;
+      _Optional_payload(_Optional_payload&&) = default;
+
+      _Optional_payload&
+      operator=(const _Optional_payload& __other) = default;
+
+      _Optional_payload&
+      operator=(_Optional_payload&& __other)
+      noexcept(__and_<is_nothrow_move_constructible<_Tp>,
+		      is_nothrow_move_assignable<_Tp>>())
       {
-	if (__other._M_engaged)
-	  this->_M_construct(__other._M_payload);
+	if (this->_M_engaged && __other._M_engaged)
+	  this->_M_get() = std::move(__other._M_get());
+	else
+	  {
+	    if (__other._M_engaged)
+	      this->_M_construct(std::move(__other._M_get()));
+	    else
+	      this->_M_reset();
+	  }
+	return *this;
       }
 
-      constexpr _Optional_payload(_Optional_payload&& __other)
+      using _Stored_type = remove_const_t<_Tp>;
+      struct _Empty_byte { };
+      union {
+          _Empty_byte _M_empty;
+          _Stored_type _M_payload;
+      };
+      bool _M_engaged;
+
+      template<typename... _Args>
+        void
+        _M_construct(_Args&&... __args)
+        noexcept(is_nothrow_constructible<_Stored_type, _Args...>())
+        {
+          ::new ((void *) std::__addressof(this->_M_payload))
+            _Stored_type(std::forward<_Args>(__args)...);
+          this->_M_engaged = true;
+        }
+
+      // The _M_get operations have _M_engaged as a precondition.
+      constexpr _Tp&
+	_M_get() noexcept
       {
-	if (__other._M_engaged)
-	  this->_M_construct(std::move(__other._M_payload));
+	return this->_M_payload;
+      }
+
+      constexpr const _Tp&
+	_M_get() const noexcept
+      {
+	return this->_M_payload;
+      }
+
+      // _M_reset is a 'safe' operation with no precondition.
+      void
+      _M_reset() noexcept
+      {
+	if (this->_M_engaged)
+	  {
+	    this->_M_engaged = false;
+	    this->_M_payload.~_Stored_type();
+	  }
       }
+    };
+
+  // Payload for optionals with non-trivial copy and move assignment.
+  template <typename _Tp>
+    struct _Optional_payload<_Tp, true, false, false>
+    {
+      constexpr _Optional_payload()
+	: _M_empty(), _M_engaged(false) {}
+
+      template<typename... _Args>
+      constexpr _Optional_payload(in_place_t, _Args&&... __args)
+	: _M_payload(std::forward<_Args>(__args)...),
+	  _M_engaged(true)
+      {}
+
+      template<typename _Up, typename... _Args>
+      constexpr _Optional_payload(std::initializer_list<_Up> __il,
+				  _Args&&... __args)
+	: _M_payload(__il, std::forward<_Args>(__args)...),
+	  _M_engaged(true) {}
+
+      template <class _Up> struct __ctor_tag {};
+
+      constexpr _Optional_payload(__ctor_tag<bool>,
+				  const _Tp& __other)
+	: _M_payload(__other),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(__ctor_tag<void>)
+	: _M_empty(), _M_engaged(false)
+      {}
+
+      constexpr _Optional_payload(__ctor_tag<bool>, _Tp&& __other)
+	: _M_payload(std::move(__other)),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  const _Optional_payload& __other)
+	: _Optional_payload(__engaged ?
+			    _Optional_payload(__ctor_tag<bool>{},
+					      __other._M_payload) :
+			    _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  _Optional_payload&& __other)
+	: _Optional_payload(__engaged
+			    ? _Optional_payload(__ctor_tag<bool>{},
+						std::move(__other._M_payload))
+			    : _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      _Optional_payload(const _Optional_payload&) = default;
+      _Optional_payload(_Optional_payload&&) = default;
 
       _Optional_payload&
       operator=(const _Optional_payload& __other)
@@ -238,13 +612,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
           _Empty_byte _M_empty;
           _Stored_type _M_payload;
       };
-      bool _M_engaged = false;
-
-      ~_Optional_payload()
-      {
-        if (_M_engaged)
-          _M_payload.~_Stored_type();
-      }
+      bool _M_engaged;
 
       template<typename... _Args>
         void
@@ -280,7 +648,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  }
       }
     };
-  
+
   template<typename _Tp, typename _Dp>
     class _Optional_base_impl
     {
diff --git a/libstdc++-v3/testsuite/20_util/optional/84601.cc b/libstdc++-v3/testsuite/20_util/optional/84601.cc
new file mode 100644
index 0000000..e86d39e
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/optional/84601.cc
@@ -0,0 +1,22 @@
+// { dg-options "-std=gnu++17" }
+// { dg-do compile }
+
+#include <optional>
+
+using pair_t = std::pair<int, int>;
+using opt_t = std::optional<pair_t>;
+
+static_assert(std::is_copy_constructible_v<opt_t::value_type>);
+static_assert(std::is_copy_assignable_v<opt_t::value_type>);
+
+static_assert(std::is_copy_assignable_v<opt_t>); // assertion fails.
+
+class A
+{
+  void f(const opt_t& opt)
+  {
+    _opt = opt;
+  }
+
+  opt_t _opt;
+};
diff --git a/libstdc++-v3/testsuite/20_util/optional/cons/value_neg.cc b/libstdc++-v3/testsuite/20_util/optional/cons/value_neg.cc
index c448996..ae55ab2 100644
--- a/libstdc++-v3/testsuite/20_util/optional/cons/value_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/optional/cons/value_neg.cc
@@ -37,8 +37,8 @@ int main()
     std::optional<std::unique_ptr<int>> oup2 = new int;  // { dg-error "conversion" }
     struct U { explicit U(std::in_place_t); };
     std::optional<U> ou(std::in_place); // { dg-error "no matching" }
-    // { dg-error "no type" "" { target { *-*-* } } 647 }
-    // { dg-error "no type" "" { target { *-*-* } } 657 }
-    // { dg-error "no type" "" { target { *-*-* } } 714 }
+    // { dg-error "no type" "" { target { *-*-* } } 1015 }
+    // { dg-error "no type" "" { target { *-*-* } } 1025 }
+    // { dg-error "no type" "" { target { *-*-* } } 1082 }
   }
 }
Jonathan Wakely March 6, 2018, 9:06 p.m. | #2
On 28/02/18 15:12 +0200, Ville Voutilainen wrote:
>-  // Payload for constexpr optionals.

>+  // Payload for optionals with non-trivial destructor.

>   template <typename _Tp,

>           bool /*_HasTrivialDestructor*/ =

>-            is_trivially_destructible<_Tp>::value>

>+            is_trivially_destructible<_Tp>::value,

>+          bool /*_HasTrivialCopyAssignment*/ =

>+            is_trivially_copy_assignable<_Tp>::value,

>+          bool /*_HasTrivialMoveAssignment*/ =

>+            is_trivially_move_assignable<_Tp>::value>


I'm not sure these comments are very useful, as they just repeat the
info that the traits already give us. Also, you could use the _v
variable templates if you wanted (doesn't make much difference
though).

But on the subject of redundant comments ...

>     struct _Optional_payload


It took me a minute to figure out which conditions the primary
template gets used for, to double-check the comment. Would it be
helpful to use a comment like:

     struct _Optional_payload // <false, _TrivialCopy, _TrivialMove>

or does that not really clarify anything?

I suppose it doesn't tell us any more than the "non-trivial
destructor" comment you already have.

So OK for trunk then.

Patch

diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional
index 01fc06b..0aa20dd 100644
--- a/libstdc++-v3/include/std/optional
+++ b/libstdc++-v3/include/std/optional
@@ -98,13 +98,137 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
   { _GLIBCXX_THROW_OR_ABORT(bad_optional_access()); }
 
 
-  // Payload for constexpr optionals.
+  // Payload for optionals with non-trivial destructor.
   template <typename _Tp,
 	    bool /*_HasTrivialDestructor*/ =
-	      is_trivially_destructible<_Tp>::value>
+	      is_trivially_destructible<_Tp>::value,
+	    bool /*_HasTrivialCopyAssignment*/ =
+	      is_trivially_copy_assignable<_Tp>::value,
+	    bool /*_HasTrivialMoveAssignment*/ =
+	      is_trivially_move_assignable<_Tp>::value>
     struct _Optional_payload
     {
       constexpr _Optional_payload()
+	: _M_empty() {}
+
+      template <typename... _Args>
+      constexpr _Optional_payload(in_place_t, _Args&&... __args)
+	: _M_payload(std::forward<_Args>(__args)...),
+	  _M_engaged(true) {}
+
+      template<typename _Up, typename... _Args>
+      constexpr _Optional_payload(std::initializer_list<_Up> __il,
+				  _Args&&... __args)
+	: _M_payload(__il, std::forward<_Args>(__args)...),
+	  _M_engaged(true) {}
+      constexpr
+      _Optional_payload(bool __engaged, const _Optional_payload& __other)
+	: _Optional_payload(__other)
+      {}
+
+      constexpr
+      _Optional_payload(bool __engaged, _Optional_payload&& __other)
+	: _Optional_payload(std::move(__other))
+      {}
+
+      constexpr _Optional_payload(const _Optional_payload& __other)
+      {
+	if (__other._M_engaged)
+	  this->_M_construct(__other._M_payload);
+      }
+
+      constexpr _Optional_payload(_Optional_payload&& __other)
+      {
+	if (__other._M_engaged)
+	  this->_M_construct(std::move(__other._M_payload));
+      }
+
+      _Optional_payload&
+      operator=(const _Optional_payload& __other)
+      {
+        if (this->_M_engaged && __other._M_engaged)
+          this->_M_get() = __other._M_get();
+        else
+	  {
+	    if (__other._M_engaged)
+	      this->_M_construct(__other._M_get());
+	    else
+	      this->_M_reset();
+	  }
+	return *this;
+      }
+
+      _Optional_payload&
+      operator=(_Optional_payload&& __other)
+      noexcept(__and_<is_nothrow_move_constructible<_Tp>,
+		      is_nothrow_move_assignable<_Tp>>())
+      {
+	if (this->_M_engaged && __other._M_engaged)
+	  this->_M_get() = std::move(__other._M_get());
+	else
+	  {
+	    if (__other._M_engaged)
+	      this->_M_construct(std::move(__other._M_get()));
+	    else
+	      this->_M_reset();
+	  }
+	return *this;
+      }
+
+      using _Stored_type = remove_const_t<_Tp>;
+      struct _Empty_byte { };
+      union {
+          _Empty_byte _M_empty;
+          _Stored_type _M_payload;
+      };
+      bool _M_engaged = false;
+
+      ~_Optional_payload()
+      {
+        if (_M_engaged)
+          _M_payload.~_Stored_type();
+      }
+
+      template<typename... _Args>
+        void
+        _M_construct(_Args&&... __args)
+        noexcept(is_nothrow_constructible<_Stored_type, _Args...>())
+        {
+          ::new ((void *) std::__addressof(this->_M_payload))
+            _Stored_type(std::forward<_Args>(__args)...);
+          this->_M_engaged = true;
+        }
+
+      // The _M_get operations have _M_engaged as a precondition.
+      constexpr _Tp&
+	_M_get() noexcept
+      {
+	return this->_M_payload;
+      }
+
+      constexpr const _Tp&
+	_M_get() const noexcept
+      {
+	return this->_M_payload;
+      }
+
+      // _M_reset is a 'safe' operation with no precondition.
+      void
+      _M_reset() noexcept
+      {
+	if (this->_M_engaged)
+	  {
+	    this->_M_engaged = false;
+	    this->_M_payload.~_Stored_type();
+	  }
+      }
+    };
+
+  // Payload for constexpr optionals.
+  template <typename _Tp>
+    struct _Optional_payload<_Tp, true, true, true>
+    {
+      constexpr _Optional_payload()
 	: _M_empty(), _M_engaged(false) {}
 
       template<typename... _Args>
@@ -161,44 +285,294 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       bool _M_engaged;
     };
 
-  // Payload for optionals with non-trivial destructor.
+  // Payload for optionals with non-trivial copy assignment.
   template <typename _Tp>
-    struct _Optional_payload<_Tp, false>
+    struct _Optional_payload<_Tp, true, false, true>
     {
       constexpr _Optional_payload()
-	: _M_empty() {}
+	: _M_empty(), _M_engaged(false) {}
 
-      template <typename... _Args>
+      template<typename... _Args>
       constexpr _Optional_payload(in_place_t, _Args&&... __args)
 	: _M_payload(std::forward<_Args>(__args)...),
+	  _M_engaged(true)
+      {}
+
+      template<typename _Up, typename... _Args>
+      constexpr _Optional_payload(std::initializer_list<_Up> __il,
+				  _Args&&... __args)
+	: _M_payload(__il, std::forward<_Args>(__args)...),
 	  _M_engaged(true) {}
 
+      template <class _Up> struct __ctor_tag {};
+
+      constexpr _Optional_payload(__ctor_tag<bool>,
+				  const _Tp& __other)
+	: _M_payload(__other),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(__ctor_tag<void>)
+	: _M_empty(), _M_engaged(false)
+      {}
+
+      constexpr _Optional_payload(__ctor_tag<bool>, _Tp&& __other)
+	: _M_payload(std::move(__other)),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  const _Optional_payload& __other)
+	: _Optional_payload(__engaged ?
+			    _Optional_payload(__ctor_tag<bool>{},
+					      __other._M_payload) :
+			    _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  _Optional_payload&& __other)
+	: _Optional_payload(__engaged
+			    ? _Optional_payload(__ctor_tag<bool>{},
+						std::move(__other._M_payload))
+			    : _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      _Optional_payload(const _Optional_payload&) = default;
+      _Optional_payload(_Optional_payload&&) = default;
+
+      _Optional_payload&
+      operator=(const _Optional_payload& __other)
+      {
+        if (this->_M_engaged && __other._M_engaged)
+          this->_M_get() = __other._M_get();
+        else
+	  {
+	    if (__other._M_engaged)
+	      this->_M_construct(__other._M_get());
+	    else
+	      this->_M_reset();
+	  }
+	return *this;
+      }
+
+      _Optional_payload&
+      operator=(_Optional_payload&& __other) = default;
+
+      using _Stored_type = remove_const_t<_Tp>;
+      struct _Empty_byte { };
+      union {
+          _Empty_byte _M_empty;
+          _Stored_type _M_payload;
+      };
+      bool _M_engaged;
+
+      template<typename... _Args>
+        void
+        _M_construct(_Args&&... __args)
+        noexcept(is_nothrow_constructible<_Stored_type, _Args...>())
+        {
+          ::new ((void *) std::__addressof(this->_M_payload))
+            _Stored_type(std::forward<_Args>(__args)...);
+          this->_M_engaged = true;
+        }
+
+      // The _M_get operations have _M_engaged as a precondition.
+      constexpr _Tp&
+	_M_get() noexcept
+      {
+	return this->_M_payload;
+      }
+
+      constexpr const _Tp&
+	_M_get() const noexcept
+      {
+	return this->_M_payload;
+      }
+
+      // _M_reset is a 'safe' operation with no precondition.
+      void
+      _M_reset() noexcept
+      {
+	if (this->_M_engaged)
+	  {
+	    this->_M_engaged = false;
+	    this->_M_payload.~_Stored_type();
+	  }
+      }
+    };
+
+  // Payload for optionals with non-trivial move assignment.
+  template <typename _Tp>
+    struct _Optional_payload<_Tp, true, true, false>
+    {
+      constexpr _Optional_payload()
+	: _M_empty(), _M_engaged(false) {}
+
+      template<typename... _Args>
+      constexpr _Optional_payload(in_place_t, _Args&&... __args)
+	: _M_payload(std::forward<_Args>(__args)...),
+	  _M_engaged(true)
+      {}
+
       template<typename _Up, typename... _Args>
       constexpr _Optional_payload(std::initializer_list<_Up> __il,
 				  _Args&&... __args)
 	: _M_payload(__il, std::forward<_Args>(__args)...),
 	  _M_engaged(true) {}
-      constexpr
-      _Optional_payload(bool __engaged, const _Optional_payload& __other)
-	: _Optional_payload(__other)
+
+      template <class _Up> struct __ctor_tag {};
+
+      constexpr _Optional_payload(__ctor_tag<bool>,
+				  const _Tp& __other)
+	: _M_payload(__other),
+	  _M_engaged(true)
       {}
 
-      constexpr
-      _Optional_payload(bool __engaged, _Optional_payload&& __other)
-	: _Optional_payload(std::move(__other))
+      constexpr _Optional_payload(__ctor_tag<void>)
+	: _M_empty(), _M_engaged(false)
       {}
 
-      constexpr _Optional_payload(const _Optional_payload& __other)
+      constexpr _Optional_payload(__ctor_tag<bool>, _Tp&& __other)
+	: _M_payload(std::move(__other)),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  const _Optional_payload& __other)
+	: _Optional_payload(__engaged ?
+			    _Optional_payload(__ctor_tag<bool>{},
+					      __other._M_payload) :
+			    _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  _Optional_payload&& __other)
+	: _Optional_payload(__engaged
+			    ? _Optional_payload(__ctor_tag<bool>{},
+						std::move(__other._M_payload))
+			    : _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      _Optional_payload(const _Optional_payload&) = default;
+      _Optional_payload(_Optional_payload&&) = default;
+
+      _Optional_payload&
+      operator=(const _Optional_payload& __other) = default;
+
+      _Optional_payload&
+      operator=(_Optional_payload&& __other)
+      noexcept(__and_<is_nothrow_move_constructible<_Tp>,
+		      is_nothrow_move_assignable<_Tp>>())
       {
-	if (__other._M_engaged)
-	  this->_M_construct(__other._M_payload);
+	if (this->_M_engaged && __other._M_engaged)
+	  this->_M_get() = std::move(__other._M_get());
+	else
+	  {
+	    if (__other._M_engaged)
+	      this->_M_construct(std::move(__other._M_get()));
+	    else
+	      this->_M_reset();
+	  }
+	return *this;
       }
 
-      constexpr _Optional_payload(_Optional_payload&& __other)
+      using _Stored_type = remove_const_t<_Tp>;
+      struct _Empty_byte { };
+      union {
+          _Empty_byte _M_empty;
+          _Stored_type _M_payload;
+      };
+      bool _M_engaged;
+
+      template<typename... _Args>
+        void
+        _M_construct(_Args&&... __args)
+        noexcept(is_nothrow_constructible<_Stored_type, _Args...>())
+        {
+          ::new ((void *) std::__addressof(this->_M_payload))
+            _Stored_type(std::forward<_Args>(__args)...);
+          this->_M_engaged = true;
+        }
+
+      // The _M_get operations have _M_engaged as a precondition.
+      constexpr _Tp&
+	_M_get() noexcept
       {
-	if (__other._M_engaged)
-	  this->_M_construct(std::move(__other._M_payload));
+	return this->_M_payload;
+      }
+
+      constexpr const _Tp&
+	_M_get() const noexcept
+      {
+	return this->_M_payload;
+      }
+
+      // _M_reset is a 'safe' operation with no precondition.
+      void
+      _M_reset() noexcept
+      {
+	if (this->_M_engaged)
+	  {
+	    this->_M_engaged = false;
+	    this->_M_payload.~_Stored_type();
+	  }
       }
+    };
+
+  // Payload for optionals with non-trivial copy and move assignment.
+  template <typename _Tp>
+    struct _Optional_payload<_Tp, true, false, false>
+    {
+      constexpr _Optional_payload()
+	: _M_empty(), _M_engaged(false) {}
+
+      template<typename... _Args>
+      constexpr _Optional_payload(in_place_t, _Args&&... __args)
+	: _M_payload(std::forward<_Args>(__args)...),
+	  _M_engaged(true)
+      {}
+
+      template<typename _Up, typename... _Args>
+      constexpr _Optional_payload(std::initializer_list<_Up> __il,
+				  _Args&&... __args)
+	: _M_payload(__il, std::forward<_Args>(__args)...),
+	  _M_engaged(true) {}
+
+      template <class _Up> struct __ctor_tag {};
+
+      constexpr _Optional_payload(__ctor_tag<bool>,
+				  const _Tp& __other)
+	: _M_payload(__other),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(__ctor_tag<void>)
+	: _M_empty(), _M_engaged(false)
+      {}
+
+      constexpr _Optional_payload(__ctor_tag<bool>, _Tp&& __other)
+	: _M_payload(std::move(__other)),
+	  _M_engaged(true)
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  const _Optional_payload& __other)
+	: _Optional_payload(__engaged ?
+			    _Optional_payload(__ctor_tag<bool>{},
+					      __other._M_payload) :
+			    _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      constexpr _Optional_payload(bool __engaged,
+				  _Optional_payload&& __other)
+	: _Optional_payload(__engaged
+			    ? _Optional_payload(__ctor_tag<bool>{},
+						std::move(__other._M_payload))
+			    : _Optional_payload(__ctor_tag<void>{}))
+      {}
+
+      _Optional_payload(const _Optional_payload&) = default;
+      _Optional_payload(_Optional_payload&&) = default;
 
       _Optional_payload&
       operator=(const _Optional_payload& __other)
@@ -238,13 +612,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
           _Empty_byte _M_empty;
           _Stored_type _M_payload;
       };
-      bool _M_engaged = false;
-
-      ~_Optional_payload()
-      {
-        if (_M_engaged)
-          _M_payload.~_Stored_type();
-      }
+      bool _M_engaged;
 
       template<typename... _Args>
         void
@@ -280,7 +648,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	  }
       }
     };
-  
+
   template<typename _Tp, typename _Dp>
     class _Optional_base_impl
     {
diff --git a/libstdc++-v3/testsuite/20_util/optional/84601.cc b/libstdc++-v3/testsuite/20_util/optional/84601.cc
new file mode 100644
index 0000000..e86d39e
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/optional/84601.cc
@@ -0,0 +1,22 @@ 
+// { dg-options "-std=gnu++17" }
+// { dg-do compile }
+
+#include <optional>
+
+using pair_t = std::pair<int, int>;
+using opt_t = std::optional<pair_t>;
+
+static_assert(std::is_copy_constructible_v<opt_t::value_type>);
+static_assert(std::is_copy_assignable_v<opt_t::value_type>);
+
+static_assert(std::is_copy_assignable_v<opt_t>); // assertion fails.
+
+class A
+{
+  void f(const opt_t& opt)
+  {
+    _opt = opt;
+  }
+
+  opt_t _opt;
+};