diff mbox series

PR libstdc++/86963 Implement LWG 2729 constraints on tuple assignment

Message ID 20180817175246.GA18016@redhat.com
State New
Headers show
Series PR libstdc++/86963 Implement LWG 2729 constraints on tuple assignment | expand

Commit Message

Jonathan Wakely Aug. 17, 2018, 5:52 p.m. UTC
PR libstdc++/86963
	* include/std/tuple (__tuple_base): New class template with deleted
	copy assignment operator.
	(tuple, tuple<_T1, _T2>): Derive from __tuple_base<tuple> so that
	implicit copy/move assignment operator will be deleted/suppressed.
	(tuple::__assignable, tuple<_T1, _T2>::__assignable): New helper
	functions for SFINAE constraints on assignment operators.
	(tuple::__nothrow_assignable, tuple<_T1, _T2>::__nothrow_assignable):
	New helper functions for exception specifications.
	(tuple::operator=(const tuple&), tuple::operator=(tuple&&))
	(tuple<_T1, _T2>::operator=(const tuple&))
	(tuple<_T1, _T2>::operator=(tuple&&)): Change parameter types to
	__nonesuch_no_braces when the operator should be defined implicitly.
	Use __nothrow_assignable for exception specifications.
	(tuple::operator=(const tuple<_UElements...>&))
	(tuple::operator=(tuple<_UElements...>&&))
	(tuple<_T1, _T2>::operator=(const tuple<_U1, _U2>&))
	(tuple<_T1, _T2>::operator=(tuple<_U1, _U2>&&))
	(tuple<_T1, _T2>::operator=(const pair<_U1, _U2>&))
	(tuple<_T1, _T2>::operator=(pair<_U1, _U2>&&)): Constrain using
	__assignable and use __nothrow_assignable for exception
	specifications.
	* python/libstdcxx/v6/printers.py (is_specialization_of): Accept
	gdb.Type as first argument, instead of a string.
	(StdTuplePrinter._iterator._is_nonempty_tuple): New method to check
	tuple for expected structure.
	(StdTuplePrinter._iterator.__init__): Use _is_nonempty_tuple.
	* testsuite/20_util/tuple/dr2729.cc: New test.
	* testsuite/20_util/tuple/element_access/get_neg.cc: Change dg-error
	to dg-prune-output.

Tested x86_64-linux, committed to trunk.
commit 6f8a632c16f417ac0e0db1b2a02a27708702279d
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Fri Aug 17 16:12:21 2018 +0100

    PR libstdc++/86963 Implement LWG 2729 constraints on tuple assignment
    
            PR libstdc++/86963
            * include/std/tuple (__tuple_base): New class template with deleted
            copy assignment operator.
            (tuple, tuple<_T1, _T2>): Derive from __tuple_base<tuple> so that
            implicit copy/move assignment operator will be deleted/suppressed.
            (tuple::__assignable, tuple<_T1, _T2>::__assignable): New helper
            functions for SFINAE constraints on assignment operators.
            (tuple::__nothrow_assignable, tuple<_T1, _T2>::__nothrow_assignable):
            New helper functions for exception specifications.
            (tuple::operator=(const tuple&), tuple::operator=(tuple&&))
            (tuple<_T1, _T2>::operator=(const tuple&))
            (tuple<_T1, _T2>::operator=(tuple&&)): Change parameter types to
            __nonesuch_no_braces when the operator should be defined implicitly.
            Use __nothrow_assignable for exception specifications.
            (tuple::operator=(const tuple<_UElements...>&))
            (tuple::operator=(tuple<_UElements...>&&))
            (tuple<_T1, _T2>::operator=(const tuple<_U1, _U2>&))
            (tuple<_T1, _T2>::operator=(tuple<_U1, _U2>&&))
            (tuple<_T1, _T2>::operator=(const pair<_U1, _U2>&))
            (tuple<_T1, _T2>::operator=(pair<_U1, _U2>&&)): Constrain using
            __assignable and use __nothrow_assignable for exception
            specifications.
            * python/libstdcxx/v6/printers.py (is_specialization_of): Accept
            gdb.Type as first argument, instead of a string.
            (StdTuplePrinter._iterator._is_nonempty_tuple): New method to check
            tuple for expected structure.
            (StdTuplePrinter._iterator.__init__): Use _is_nonempty_tuple.
            * testsuite/20_util/tuple/dr2729.cc: New test.
            * testsuite/20_util/tuple/element_access/get_neg.cc: Change dg-error
            to dg-prune-output.

Comments

Jonathan Wakely Aug. 17, 2018, 6:01 p.m. UTC | #1
On 17/08/18 18:52 +0100, Jonathan Wakely wrote:
>+  // The tag parameter ensures that in nested tuples each __tuple_base
>+  // is a different type and can use the empty base-class optimisation.
>+  template<typename _Tag>
>+    class __tuple_base

Specifically, this would fail if __tuple_base was not a class
template:

static_assert(sizeof(tuple<tuple<int>>) == sizeof(int), "");

And also:

struct empty {};
static_assert(sizeof(tuple<tuple<empty>, tuple<empty>>) == 2, "");
Jonathan Wakely Aug. 17, 2018, 6:54 p.m. UTC | #2
On 17/08/18 19:01 +0100, Jonathan Wakely wrote:
>On 17/08/18 18:52 +0100, Jonathan Wakely wrote:
>>+  // The tag parameter ensures that in nested tuples each __tuple_base
>>+  // is a different type and can use the empty base-class optimisation.
>>+  template<typename _Tag>
>>+    class __tuple_base
>
>Specifically, this would fail if __tuple_base was not a class
>template:
>
>static_assert(sizeof(tuple<tuple<int>>) == sizeof(int), "");
>
>And also:
>
>struct empty {};
>static_assert(sizeof(tuple<tuple<empty>, tuple<empty>>) == 2, "");


In fact, it's just occurred to me that we don't really need the
__tuple_base class template at all. We can make _Tuple_impl
non-assignable (adding _M_assign members for the required
functionality). Then we don't need an extra base class.
Jonathan Wakely Aug. 20, 2018, 1:53 p.m. UTC | #3
On 17/08/18 19:54 +0100, Jonathan Wakely wrote:
>On 17/08/18 19:01 +0100, Jonathan Wakely wrote:
>>On 17/08/18 18:52 +0100, Jonathan Wakely wrote:
>>>+  // The tag parameter ensures that in nested tuples each __tuple_base
>>>+  // is a different type and can use the empty base-class optimisation.
>>>+  template<typename _Tag>
>>>+    class __tuple_base
>>
>>Specifically, this would fail if __tuple_base was not a class
>>template:
>>
>>static_assert(sizeof(tuple<tuple<int>>) == sizeof(int), "");
>>
>>And also:
>>
>>struct empty {};
>>static_assert(sizeof(tuple<tuple<empty>, tuple<empty>>) == 2, "");
>
>
>In fact, it's just occurred to me that we don't really need the
>__tuple_base class template at all. We can make _Tuple_impl
>non-assignable (adding _M_assign members for the required
>functionality). Then we don't need an extra base class.

Which is what this patch does. I think it's cleaner than needing the
__tuple_base base class.

Tested x86_64-linux and committed to trunk.
commit 1af2f8e775e0f742b530912a3e988316f2c74375
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Fri Aug 17 20:37:29 2018 +0100

    PR libstdc++/86963 Remove use of __tuple_base in std::tuple
    
    The _Tuple_impl base class can be used to disable copy/move assignment,
    without requiring an extra base class.
    
    Exception specifications on std::tuple assignment and swap functions can
    be defined directly using is_nothrow_swappable, instead of querying the
    base classes.
    
            PR libstdc++/86963
            * include/std/tuple (_Tuple_impl::operator=): Define as deleted.
            (_Tuple_impl::_M_assign): New functions to perform assignment instead
            of assignment operators.
            (_Tuple_impl::_M_swap): Remove exception specification.
            (_Tuple_impl<_Idx, _Head>): Likewise.
            (_TC::_NonNestedTuple, _TC::_NotSameTuple): Use __remove_cvref_t.
            (__tuple_base): Remove.
            (tuple, tuple<_T1, _T2>): Remove inheritance from __tuple_base.
            (tuple::operator=, tuple<_T1, _T2>::operator=): Call _M_assign.
            (tuple::swap, tuple<_T1, _T2>::swap): Define exception specification
            using __is_nothrow_swappable.
            (tuple<_T1, _T2>::tuple(_U1&&, _U2&&)): Use __remove_cvref_t.

diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index 955b853066f..56b97c25eed 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -219,6 +219,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       constexpr _Tuple_impl(const _Tuple_impl&) = default;
 
+      // _GLIBCXX_RESOLVE_LIB_DEFECTS
+      // 2729. Missing SFINAE on std::pair::operator=
+      _Tuple_impl& operator=(const _Tuple_impl&) = delete;
+
       constexpr
       _Tuple_impl(_Tuple_impl&& __in)
       noexcept(__and_<is_nothrow_move_constructible<_Head>,
@@ -288,49 +292,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                 std::forward<_UHead>
 		(_Tuple_impl<_Idx, _UHead, _UTails...>::_M_head(__in))) { }
 
-      _Tuple_impl&
-      operator=(const _Tuple_impl& __in)
-      {
-	_M_head(*this) = _M_head(__in);
-	_M_tail(*this) = _M_tail(__in);
-	return *this;
-      }
-
-      _Tuple_impl&
-      operator=(_Tuple_impl&& __in)
-      noexcept(__and_<is_nothrow_move_assignable<_Head>,
-	              is_nothrow_move_assignable<_Inherited>>::value)
-      {
-	_M_head(*this) = std::forward<_Head>(_M_head(__in));
-	_M_tail(*this) = std::move(_M_tail(__in));
-	return *this;
-      }
-
       template<typename... _UElements>
-        _Tuple_impl&
-        operator=(const _Tuple_impl<_Idx, _UElements...>& __in)
+        void
+        _M_assign(const _Tuple_impl<_Idx, _UElements...>& __in)
         {
 	  _M_head(*this) = _Tuple_impl<_Idx, _UElements...>::_M_head(__in);
-	  _M_tail(*this) = _Tuple_impl<_Idx, _UElements...>::_M_tail(__in);
-	  return *this;
+	  _M_tail(*this)._M_assign(
+	      _Tuple_impl<_Idx, _UElements...>::_M_tail(__in));
 	}
 
       template<typename _UHead, typename... _UTails>
-        _Tuple_impl&
-        operator=(_Tuple_impl<_Idx, _UHead, _UTails...>&& __in)
+        void
+        _M_assign(_Tuple_impl<_Idx, _UHead, _UTails...>&& __in)
         {
 	  _M_head(*this) = std::forward<_UHead>
 	    (_Tuple_impl<_Idx, _UHead, _UTails...>::_M_head(__in));
-	  _M_tail(*this) = std::move
-	    (_Tuple_impl<_Idx, _UHead, _UTails...>::_M_tail(__in));
-	  return *this;
+	  _M_tail(*this)._M_assign(
+	      std::move(_Tuple_impl<_Idx, _UHead, _UTails...>::_M_tail(__in)));
 	}
 
     protected:
       void
       _M_swap(_Tuple_impl& __in)
-      noexcept(__is_nothrow_swappable<_Head>::value
-               && noexcept(_M_tail(__in)._M_swap(_M_tail(__in))))
       {
 	using std::swap;
 	swap(_M_head(*this), _M_head(__in));
@@ -367,6 +350,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       constexpr _Tuple_impl(const _Tuple_impl&) = default;
 
+      // _GLIBCXX_RESOLVE_LIB_DEFECTS
+      // 2729. Missing SFINAE on std::pair::operator=
+      _Tuple_impl& operator=(const _Tuple_impl&) = delete;
+
       constexpr
       _Tuple_impl(_Tuple_impl&& __in)
       noexcept(is_nothrow_move_constructible<_Head>::value)
@@ -420,42 +407,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                 std::forward<_UHead>(_Tuple_impl<_Idx, _UHead>::_M_head(__in)))
 	{ }
 
-      _Tuple_impl&
-      operator=(const _Tuple_impl& __in)
-      {
-	_M_head(*this) = _M_head(__in);
-	return *this;
-      }
-
-      _Tuple_impl&
-      operator=(_Tuple_impl&& __in)
-      noexcept(is_nothrow_move_assignable<_Head>::value)
-      {
-	_M_head(*this) = std::forward<_Head>(_M_head(__in));
-	return *this;
-      }
-
       template<typename _UHead>
-        _Tuple_impl&
-        operator=(const _Tuple_impl<_Idx, _UHead>& __in)
+        void
+        _M_assign(const _Tuple_impl<_Idx, _UHead>& __in)
         {
 	  _M_head(*this) = _Tuple_impl<_Idx, _UHead>::_M_head(__in);
-	  return *this;
 	}
 
       template<typename _UHead>
-        _Tuple_impl&
-        operator=(_Tuple_impl<_Idx, _UHead>&& __in)
+        void
+        _M_assign(_Tuple_impl<_Idx, _UHead>&& __in)
         {
 	  _M_head(*this)
 	    = std::forward<_UHead>(_Tuple_impl<_Idx, _UHead>::_M_head(__in));
-	  return *this;
 	}
 
     protected:
       void
       _M_swap(_Tuple_impl& __in)
-      noexcept(__is_nothrow_swappable<_Head>::value)
       {
 	using std::swap;
 	swap(_M_head(*this), _M_head(__in));
@@ -495,20 +464,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     static constexpr bool _NonNestedTuple()
     {
       return  __and_<__not_<is_same<tuple<_Elements...>,
-                                   typename remove_cv<
-                                     typename remove_reference<_SrcTuple>::type
-                                   >::type>>,
+				    __remove_cvref_t<_SrcTuple>>>,
                      __not_<is_convertible<_SrcTuple, _Elements...>>,
                      __not_<is_constructible<_Elements..., _SrcTuple>>
               >::value;
     }
+
     template<typename... _UElements>
     static constexpr bool _NotSameTuple()
     {
       return  __not_<is_same<tuple<_Elements...>,
-			     typename remove_const<
-			       typename remove_reference<_UElements...>::type
-			       >::type>>::value;
+			     __remove_cvref_t<_UElements>...>>::value;
     }
   };
 
@@ -544,30 +510,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     {
       return true;
     }
+
     template<typename... _UElements>
     static constexpr bool _NotSameTuple()
     {
-      return  true;
+      return true;
     }
   };
 
-  // The tag parameter ensures that in nested tuples each __tuple_base
-  // is a different type and can use the empty base-class optimisation.
-  template<typename _Tag>
-    class __tuple_base
-    {
-      template<typename...> friend struct tuple;
-      __tuple_base() = default;
-      ~__tuple_base() = default;
-      __tuple_base(const __tuple_base&) = default;
-      __tuple_base& operator=(const __tuple_base&) = delete;
-    };
-
   /// Primary class template, tuple
   template<typename... _Elements>
-    class tuple
-    : public _Tuple_impl<0, _Elements...>,
-      private __tuple_base<tuple<_Elements...>>
+    class tuple : public _Tuple_impl<0, _Elements...>
     {
       typedef _Tuple_impl<0, _Elements...> _Inherited;
 
@@ -858,13 +811,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	             static_cast<_Tuple_impl<0, _UElements...>&&>(__in))
 	{ }
 
+      // tuple assignment
+
       tuple&
       operator=(typename conditional<__assignable<const _Elements&...>(),
 				     const tuple&,
 				     const __nonesuch_no_braces&>::type __in)
       noexcept(__nothrow_assignable<const _Elements&...>())
       {
-	static_cast<_Inherited&>(*this) = __in;
+	this->_M_assign(__in);
 	return *this;
       }
 
@@ -874,7 +829,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 				     __nonesuch_no_braces&&>::type __in)
       noexcept(__nothrow_assignable<_Elements...>())
       {
-	static_cast<_Inherited&>(*this) = std::move(__in);
+	this->_M_assign(std::move(__in));
 	return *this;
       }
 
@@ -883,7 +838,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	operator=(const tuple<_UElements...>& __in)
 	noexcept(__nothrow_assignable<const _UElements&...>())
 	{
-	  static_cast<_Inherited&>(*this) = __in;
+	  this->_M_assign(__in);
 	  return *this;
 	}
 
@@ -892,13 +847,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	operator=(tuple<_UElements...>&& __in)
 	noexcept(__nothrow_assignable<_UElements...>())
 	{
-	  static_cast<_Inherited&>(*this) = std::move(__in);
+	  this->_M_assign(std::move(__in));
 	  return *this;
 	}
 
+      // tuple swap
       void
       swap(tuple& __in)
-      noexcept(noexcept(__in._M_swap(__in)))
+      noexcept(__and_<__is_nothrow_swappable<_Elements>...>::value)
       { _Inherited::_M_swap(__in); }
     };
 
@@ -934,9 +890,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   /// Partial specialization, 2-element tuple.
   /// Includes construction and assignment from a pair.
   template<typename _T1, typename _T2>
-    class tuple<_T1, _T2>
-    : public _Tuple_impl<0, _T1, _T2>,
-      private __tuple_base<tuple<_T1, _T2>>
+    class tuple<_T1, _T2> : public _Tuple_impl<0, _T1, _T2>
     {
       typedef _Tuple_impl<0, _T1, _T2> _Inherited;
 
@@ -1009,8 +963,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                     _MoveConstructibleTuple<_U1, _U2>()
                   && _TMC::template
                     _ImplicitlyMoveConvertibleTuple<_U1, _U2>()
-	          && !is_same<typename decay<_U1>::type,
-			      allocator_arg_t>::value,
+	          && !is_same<__remove_cvref_t<_U1>, allocator_arg_t>::value,
 	bool>::type = true>
         constexpr tuple(_U1&& __a1, _U2&& __a2)
 	: _Inherited(std::forward<_U1>(__a1), std::forward<_U2>(__a2)) { }
@@ -1020,8 +973,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                     _MoveConstructibleTuple<_U1, _U2>()
                   && !_TMC::template
                     _ImplicitlyMoveConvertibleTuple<_U1, _U2>()
-	          && !is_same<typename decay<_U1>::type,
-			      allocator_arg_t>::value,
+	          && !is_same<__remove_cvref_t<_U1>, allocator_arg_t>::value,
 	bool>::type = false>
         explicit constexpr tuple(_U1&& __a1, _U2&& __a2)
 	: _Inherited(std::forward<_U1>(__a1), std::forward<_U2>(__a2)) { }
@@ -1255,7 +1207,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 				     const __nonesuch_no_braces&>::type __in)
       noexcept(__nothrow_assignable<const _T1&, const _T2&>())
       {
-	static_cast<_Inherited&>(*this) = __in;
+	this->_M_assign(__in);
 	return *this;
       }
 
@@ -1265,7 +1217,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 				     __nonesuch_no_braces&&>::type __in)
       noexcept(__nothrow_assignable<_T1, _T2>())
       {
-	static_cast<_Inherited&>(*this) = std::move(__in);
+	this->_M_assign(std::move(__in));
 	return *this;
       }
 
@@ -1274,7 +1226,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	operator=(const tuple<_U1, _U2>& __in)
 	noexcept(__nothrow_assignable<const _U1&, const _U2&>())
 	{
-	  static_cast<_Inherited&>(*this) = __in;
+	  this->_M_assign(__in);
 	  return *this;
 	}
 
@@ -1283,7 +1235,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	operator=(tuple<_U1, _U2>&& __in)
 	noexcept(__nothrow_assignable<_U1, _U2>())
 	{
-	  static_cast<_Inherited&>(*this) = std::move(__in);
+	  this->_M_assign(std::move(__in));
 	  return *this;
 	}
 
@@ -1309,7 +1261,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       void
       swap(tuple& __in)
-      noexcept(noexcept(__in._M_swap(__in)))
+      noexcept(__and_<__is_nothrow_swappable<_T1>,
+		      __is_nothrow_swappable<_T2>>::value)
       { _Inherited::_M_swap(__in); }
     };
diff mbox series

Patch

diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index dd7daf7f1cf..955b853066f 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -551,9 +551,23 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
   };
 
+  // The tag parameter ensures that in nested tuples each __tuple_base
+  // is a different type and can use the empty base-class optimisation.
+  template<typename _Tag>
+    class __tuple_base
+    {
+      template<typename...> friend struct tuple;
+      __tuple_base() = default;
+      ~__tuple_base() = default;
+      __tuple_base(const __tuple_base&) = default;
+      __tuple_base& operator=(const __tuple_base&) = delete;
+    };
+
   /// Primary class template, tuple
   template<typename... _Elements>
-    class tuple : public _Tuple_impl<0, _Elements...>
+    class tuple
+    : public _Tuple_impl<0, _Elements...>,
+      private __tuple_base<tuple<_Elements...>>
     {
       typedef _Tuple_impl<0, _Elements...> _Inherited;
 
@@ -573,6 +587,19 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
         }
       };
 
+      template<typename... _UElements>
+	static constexpr
+	__enable_if_t<sizeof...(_UElements) == sizeof...(_Elements), bool>
+	__assignable()
+	{ return __and_<is_assignable<_Elements&, _UElements>...>::value; }
+
+      template<typename... _UElements>
+	static constexpr bool __nothrow_assignable()
+	{
+	  return
+	    __and_<is_nothrow_assignable<_Elements&, _UElements>...>::value;
+	}
+
     public:
       template<typename _Dummy = void,
                typename enable_if<_TC2<_Dummy>::
@@ -832,36 +859,39 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	{ }
 
       tuple&
-      operator=(const tuple& __in)
+      operator=(typename conditional<__assignable<const _Elements&...>(),
+				     const tuple&,
+				     const __nonesuch_no_braces&>::type __in)
+      noexcept(__nothrow_assignable<const _Elements&...>())
       {
 	static_cast<_Inherited&>(*this) = __in;
 	return *this;
       }
 
       tuple&
-      operator=(tuple&& __in)
-      noexcept(is_nothrow_move_assignable<_Inherited>::value)
+      operator=(typename conditional<__assignable<_Elements...>(),
+				     tuple&&,
+				     __nonesuch_no_braces&&>::type __in)
+      noexcept(__nothrow_assignable<_Elements...>())
       {
 	static_cast<_Inherited&>(*this) = std::move(__in);
 	return *this;
       }
 
       template<typename... _UElements>
-	typename
-	       enable_if<sizeof...(_UElements)
-			 == sizeof...(_Elements), tuple&>::type
-        operator=(const tuple<_UElements...>& __in)
-        {
+	__enable_if_t<__assignable<const _UElements&...>(), tuple&>
+	operator=(const tuple<_UElements...>& __in)
+	noexcept(__nothrow_assignable<const _UElements&...>())
+	{
 	  static_cast<_Inherited&>(*this) = __in;
 	  return *this;
 	}
 
       template<typename... _UElements>
-	typename
-	       enable_if<sizeof...(_UElements)
-			 == sizeof...(_Elements), tuple&>::type
-        operator=(tuple<_UElements...>&& __in)
-        {
+	__enable_if_t<__assignable<_UElements...>(), tuple&>
+	operator=(tuple<_UElements...>&& __in)
+	noexcept(__nothrow_assignable<_UElements...>())
+	{
 	  static_cast<_Inherited&>(*this) = std::move(__in);
 	  return *this;
 	}
@@ -904,10 +934,26 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
   /// Partial specialization, 2-element tuple.
   /// Includes construction and assignment from a pair.
   template<typename _T1, typename _T2>
-    class tuple<_T1, _T2> : public _Tuple_impl<0, _T1, _T2>
+    class tuple<_T1, _T2>
+    : public _Tuple_impl<0, _T1, _T2>,
+      private __tuple_base<tuple<_T1, _T2>>
     {
       typedef _Tuple_impl<0, _T1, _T2> _Inherited;
 
+      template<typename _U1, typename _U2>
+	static constexpr bool __assignable()
+	{
+	  return __and_<is_assignable<_T1&, _U1>,
+			is_assignable<_T2&, _U2>>::value;
+	}
+
+      template<typename _U1, typename _U2>
+	static constexpr bool __nothrow_assignable()
+	{
+	  return __and_<is_nothrow_assignable<_T1&, _U1>,
+			is_nothrow_assignable<_T2&, _U2>>::value;
+	}
+
     public:
       template <typename _U1 = _T1,
                 typename _U2 = _T2,
@@ -915,9 +961,8 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
                                      __is_implicitly_default_constructible<_U1>,
                                      __is_implicitly_default_constructible<_U2>>
                                    ::value, bool>::type = true>
-
-      constexpr tuple()
-      : _Inherited() { }
+	constexpr tuple()
+	: _Inherited() { }
 
       template <typename _U1 = _T1,
                 typename _U2 = _T2,
@@ -929,9 +974,8 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
                       __and_<__is_implicitly_default_constructible<_U1>,
                              __is_implicitly_default_constructible<_U2>>>>
                   ::value, bool>::type = false>
-
-      explicit constexpr tuple()
-      : _Inherited() { }
+	explicit constexpr tuple()
+	: _Inherited() { }
 
       // Shortcut for the cases where constructors taking _T1, _T2
       // need to be constrained.
@@ -1206,49 +1250,58 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 		     std::forward<_U2>(__in.second)) { }
 
       tuple&
-      operator=(const tuple& __in)
+      operator=(typename conditional<__assignable<const _T1&, const _T2&>(),
+				     const tuple&,
+				     const __nonesuch_no_braces&>::type __in)
+      noexcept(__nothrow_assignable<const _T1&, const _T2&>())
       {
 	static_cast<_Inherited&>(*this) = __in;
 	return *this;
       }
 
       tuple&
-      operator=(tuple&& __in)
-      noexcept(is_nothrow_move_assignable<_Inherited>::value)
+      operator=(typename conditional<__assignable<_T1, _T2>(),
+				     tuple&&,
+				     __nonesuch_no_braces&&>::type __in)
+      noexcept(__nothrow_assignable<_T1, _T2>())
       {
 	static_cast<_Inherited&>(*this) = std::move(__in);
 	return *this;
       }
 
       template<typename _U1, typename _U2>
-        tuple&
-        operator=(const tuple<_U1, _U2>& __in)
-        {
+	__enable_if_t<__assignable<const _U1&, const _U2&>(), tuple&>
+	operator=(const tuple<_U1, _U2>& __in)
+	noexcept(__nothrow_assignable<const _U1&, const _U2&>())
+	{
 	  static_cast<_Inherited&>(*this) = __in;
 	  return *this;
 	}
 
       template<typename _U1, typename _U2>
-        tuple&
-        operator=(tuple<_U1, _U2>&& __in)
-        {
+	__enable_if_t<__assignable<_U1, _U2>(), tuple&>
+	operator=(tuple<_U1, _U2>&& __in)
+	noexcept(__nothrow_assignable<_U1, _U2>())
+	{
 	  static_cast<_Inherited&>(*this) = std::move(__in);
 	  return *this;
 	}
 
       template<typename _U1, typename _U2>
-        tuple&
-        operator=(const pair<_U1, _U2>& __in)
-        {
+	__enable_if_t<__assignable<const _U1&, const _U2&>(), tuple&>
+	operator=(const pair<_U1, _U2>& __in)
+	noexcept(__nothrow_assignable<const _U1&, const _U2&>())
+	{
 	  this->_M_head(*this) = __in.first;
 	  this->_M_tail(*this)._M_head(*this) = __in.second;
 	  return *this;
 	}
 
       template<typename _U1, typename _U2>
-        tuple&
-        operator=(pair<_U1, _U2>&& __in)
-        {
+	__enable_if_t<__assignable<_U1, _U2>(), tuple&>
+	operator=(pair<_U1, _U2>&& __in)
+	noexcept(__nothrow_assignable<_U1, _U2>())
+	{
 	  this->_M_head(*this) = std::forward<_U1>(__in.first);
 	  this->_M_tail(*this)._M_head(*this) = std::forward<_U2>(__in.second);
 	  return *this;
diff --git a/libstdc++-v3/python/libstdcxx/v6/printers.py b/libstdc++-v3/python/libstdcxx/v6/printers.py
index 43d459ec8ec..afe1b325d87 100644
--- a/libstdc++-v3/python/libstdcxx/v6/printers.py
+++ b/libstdc++-v3/python/libstdcxx/v6/printers.py
@@ -101,12 +101,14 @@  def find_type(orig, name):
 
 _versioned_namespace = '__8::'
 
-def is_specialization_of(type, template_name):
+def is_specialization_of(x, template_name):
     "Test if a type is a given template instantiation."
     global _versioned_namespace
+    if type(x) is gdb.Type:
+        x = x.tag
     if _versioned_namespace:
-        return re.match('^std::(%s)?%s<.*>$' % (_versioned_namespace, template_name), type) is not None
-    return re.match('^std::%s<.*>$' % template_name, type) is not None
+        return re.match('^std::(%s)?%s<.*>$' % (_versioned_namespace, template_name), x) is not None
+    return re.match('^std::%s<.*>$' % template_name, x) is not None
 
 def strip_versioned_namespace(typename):
     global _versioned_namespace
@@ -413,17 +415,26 @@  class StdTuplePrinter:
     "Print a std::tuple"
 
     class _iterator(Iterator):
+        @staticmethod
+        def _is_nonempty_tuple (nodes):
+            if len (nodes) == 2:
+                if is_specialization_of (nodes[1].type, '__tuple_base'):
+                    return True
+            elif len (nodes) == 1:
+                return True
+            elif len (nodes) == 0:
+                return False
+            raise ValueError("Top of tuple tree does not consist of a single node.")
+
         def __init__ (self, head):
             self.head = head
 
             # Set the base class as the initial head of the
             # tuple.
             nodes = self.head.type.fields ()
-            if len (nodes) == 1:
+            if self._is_nonempty_tuple (nodes):
                 # Set the actual head to the first pair.
                 self.head  = self.head.cast (nodes[0].type)
-            elif len (nodes) != 0:
-                raise ValueError("Top of tuple tree does not consist of a single node.")
             self.count = 0
 
         def __iter__ (self):
diff --git a/libstdc++-v3/testsuite/20_util/tuple/dr2729.cc b/libstdc++-v3/testsuite/20_util/tuple/dr2729.cc
new file mode 100644
index 00000000000..c3863557994
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/tuple/dr2729.cc
@@ -0,0 +1,179 @@ 
+// Copyright (C) 2018 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do compile { target c++11 } }
+
+#include <tuple>
+#include <testsuite_tr1.h>
+
+using std::tuple;
+using std::pair;
+using __gnu_test::assign::AnyAssign;
+using __gnu_test::assign::DelAnyAssign;
+using __gnu_test::assign::DelCopyAssign;
+using __gnu_test::CopyConsOnlyType;
+
+// Copy assignment:
+template<typename T>
+constexpr bool copy() { return std::is_copy_assignable<T>::value; }
+
+// Move assigmment:
+template<typename T>
+constexpr bool move() { return std::is_move_assignable<T>::value; }
+
+static_assert( copy<tuple<>>(), "");
+static_assert( move<tuple<>>(), "");
+
+static_assert( copy<tuple<int>>(), "");
+static_assert( copy<tuple<AnyAssign>>(), "");
+static_assert( copy<tuple<int, int>>(), "");
+static_assert( copy<tuple<AnyAssign, AnyAssign>>(), "");
+static_assert( copy<tuple<int, AnyAssign>>(), "");
+static_assert( copy<tuple<AnyAssign, int>>(), "");
+static_assert( copy<tuple<int, int, int>>(), "");
+static_assert( copy<tuple<AnyAssign, AnyAssign, AnyAssign>>(), "");
+static_assert( copy<tuple<int, AnyAssign, AnyAssign>>(), "");
+static_assert( copy<tuple<AnyAssign, int, AnyAssign>>(), "");
+static_assert( copy<tuple<AnyAssign, AnyAssign, int>>(), "");
+
+static_assert( move<tuple<int>>(), "");
+static_assert( move<tuple<AnyAssign>>(), "");
+static_assert( move<tuple<int, int>>(), "");
+static_assert( move<tuple<AnyAssign, AnyAssign>>(), "");
+static_assert( move<tuple<int, AnyAssign>>(), "");
+static_assert( move<tuple<AnyAssign, int>>(), "");
+static_assert( move<tuple<int, int, int>>(), "");
+static_assert( move<tuple<AnyAssign, AnyAssign, AnyAssign>>(), "");
+static_assert( move<tuple<int, AnyAssign, AnyAssign>>(), "");
+static_assert( move<tuple<AnyAssign, int, AnyAssign>>(), "");
+static_assert( move<tuple<AnyAssign, AnyAssign, int>>(), "");
+
+static_assert( ! copy<tuple<DelCopyAssign>>(), "");
+static_assert( ! copy<tuple<DelCopyAssign, int>>(), "");
+static_assert( ! copy<tuple<int, DelCopyAssign>>(), "");
+static_assert( ! copy<tuple<DelCopyAssign, int, int>>(), "");
+static_assert( ! copy<tuple<int, DelCopyAssign, int>>(), "");
+static_assert( ! copy<tuple<int, int, DelCopyAssign>>(), "");
+
+static_assert( move<tuple<DelCopyAssign>>(), "");
+static_assert( move<tuple<DelCopyAssign, int>>(), "");
+static_assert( move<tuple<int, DelCopyAssign>>(), "");
+static_assert( move<tuple<DelCopyAssign, int, int>>(), "");
+static_assert( move<tuple<int, DelCopyAssign, int>>(), "");
+static_assert( move<tuple<int, int, DelCopyAssign>>(), "");
+
+static_assert( ! move<tuple<CopyConsOnlyType>>(), "");
+static_assert( ! move<tuple<CopyConsOnlyType, int>>(), "");
+static_assert( ! move<tuple<int, CopyConsOnlyType>>(), "");
+static_assert( ! move<tuple<CopyConsOnlyType, int, int>>(), "");
+static_assert( ! move<tuple<int, CopyConsOnlyType, int>>(), "");
+static_assert( ! move<tuple<int, int, CopyConsOnlyType>>(), "");
+
+// Assignment from different types of tuple (and pair):
+template<typename To, typename From>
+constexpr bool assign() { return std::is_assignable<To&, From>::value; }
+
+// 0-tuples
+static_assert( ! assign<tuple<>, tuple<int>>(), "" );
+static_assert( ! assign<tuple<>, const tuple<int>&>(), "" );
+
+// 1-tuples
+static_assert( ! assign<tuple<int>, tuple<>>(), "" );
+static_assert( ! assign<tuple<int>, const tuple<>&>(), "" );
+static_assert( ! assign<tuple<AnyAssign>, tuple<>>(), "" );
+static_assert( ! assign<tuple<AnyAssign>, tuple<int, int>>(), "" );
+static_assert( ! assign<tuple<AnyAssign>, pair<int, int>>(), "" );
+
+static_assert( ! assign<tuple<void*>, tuple<int>>(), "" );
+static_assert( ! assign<tuple<void*>, const tuple<int>&>(), "" );
+
+static_assert( assign<tuple<long>, tuple<int>>(), "" );
+static_assert( assign<tuple<long>, tuple<int>&>(), "" );
+static_assert( assign<tuple<long>, const tuple<int>>(), "" );
+static_assert( assign<tuple<long>, const tuple<int>&>(), "" );
+
+// 2-tuples
+static_assert( assign<tuple<long, long>, tuple<int, int>>(), "" );
+static_assert( assign<tuple<long, long>, tuple<int, int>&>(), "" );
+static_assert( assign<tuple<long, long>, const tuple<int, int>>(), "" );
+static_assert( assign<tuple<long, long>, const tuple<int, int>&>(), "" );
+
+static_assert( assign<tuple<long, long>, pair<int, int>>(), "" );
+static_assert( assign<tuple<long, long>, const pair<int, int>&>(), "" );
+static_assert( assign<tuple<long, long>, pair<int, int>>(), "" );
+static_assert( assign<tuple<long, long>, const pair<int, int>&&>(), "" );
+
+static_assert( assign<tuple<DelCopyAssign, AnyAssign>,
+		      tuple<DelCopyAssign, int>>(), "" );
+static_assert( ! assign<tuple<DelCopyAssign, AnyAssign>,
+			tuple<DelCopyAssign, int>&>(), "" );
+static_assert( ! assign<tuple<DelCopyAssign, AnyAssign>,
+			const tuple<DelCopyAssign, int>&>(), "" );
+static_assert( ! assign<tuple<DelCopyAssign, AnyAssign>,
+			const tuple<DelCopyAssign, int>&&>(), "" );
+
+static_assert( assign<tuple<AnyAssign, DelCopyAssign>,
+		      tuple<int, DelCopyAssign>>(), "" );
+static_assert( ! assign<tuple<AnyAssign, DelCopyAssign>,
+			tuple<int, DelCopyAssign>&>(), "" );
+static_assert( ! assign<tuple<AnyAssign, DelCopyAssign>,
+			const tuple<int, DelCopyAssign>&>(), "" );
+static_assert( ! assign<tuple<AnyAssign, DelCopyAssign>,
+			const tuple<int, DelCopyAssign>&&>(), "" );
+
+static_assert( ! assign<tuple<void*, int>,
+			tuple<int, int>>(), "" );
+static_assert( ! assign<tuple<void*, int>,
+			const tuple<int, int>&>(), "" );
+
+static_assert( assign<tuple<DelCopyAssign, AnyAssign>,
+		      pair<DelCopyAssign, int>>(), "" );
+static_assert( ! assign<tuple<DelCopyAssign, AnyAssign>,
+			pair<DelCopyAssign, int>&>(), "" );
+static_assert( ! assign<tuple<DelCopyAssign, AnyAssign>,
+			const pair<DelCopyAssign, int>&>(), "" );
+static_assert( ! assign<tuple<DelCopyAssign, AnyAssign>,
+			const pair<DelCopyAssign, int>&&>(), "" );
+
+static_assert( assign<tuple<AnyAssign, DelCopyAssign>,
+		      pair<int, DelCopyAssign>>(), "" );
+static_assert( ! assign<tuple<AnyAssign, DelCopyAssign>,
+			pair<int, DelCopyAssign>&>(), "" );
+static_assert( ! assign<tuple<AnyAssign, DelCopyAssign>,
+			const pair<int, DelCopyAssign>&>(), "" );
+static_assert( ! assign<tuple<AnyAssign, DelCopyAssign>,
+			const pair<int, DelCopyAssign>&&>(), "" );
+
+static_assert( ! assign<tuple<void*, int>,
+			pair<int, int>>(), "" );
+static_assert( ! assign<tuple<void*, int>,
+			const pair<int, int>&>(), "" );
+
+// 3-tuples
+static_assert( assign<tuple<AnyAssign, DelCopyAssign, AnyAssign>,
+		      tuple<int, DelCopyAssign, int>>(), "" );
+static_assert( ! assign<tuple<AnyAssign, DelCopyAssign, AnyAssign>,
+			tuple<int, DelCopyAssign, int>&>(), "" );
+static_assert( ! assign<tuple<AnyAssign, DelCopyAssign, AnyAssign>,
+			const tuple<int, DelCopyAssign, int>&>(), "" );
+static_assert( ! assign<tuple<AnyAssign, DelCopyAssign, AnyAssign>,
+			const tuple<int, DelCopyAssign, int>&&>(), "" );
+
+static_assert( ! assign<tuple<int, void*, int>,
+			tuple<int, int, int>>(), "" );
+static_assert( ! assign<tuple<int, void*, int>,
+			const tuple<int, int, int>&>(), "" );
diff --git a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
index e76f53aff6e..d55028268eb 100644
--- a/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/tuple/element_access/get_neg.cc
@@ -17,7 +17,7 @@ 
 
 // { dg-options "-fno-show-column" }
 // { dg-do compile { target c++14 } }
-// { dg-error "in range" "" { target *-*-* } 1297 }
+// { dg-prune-output "tuple index is in range" }
 
 #include <tuple>