PR libstdc++/89130 restore support for non-MoveConstructible types
authorJonathan Wakely <jwakely@redhat.com>
Tue, 5 Feb 2019 14:45:00 +0000 (14:45 +0000)
committerJonathan Wakely <redi@gcc.gnu.org>
Tue, 5 Feb 2019 14:45:00 +0000 (14:45 +0000)
The changes to "relocate" std::vector elements can lead to new errors
outside the immediate context, because moving the elements to new
storage no longer makes use of the move-if-noexcept utilities. This
means that types with deleted moves no longer degenerate to copies, but
are just ill-formed. The errors happen while instantiating the
noexcept-specifier for __relocate_object_a, when deciding whether to try
to relocate.

This patch introduces indirections to avoid the ill-formed
instantiations of std::__relocate_object_a. In order to avoid using
if-constexpr prior to C++17 this is done by tag dispatching. After this
patch all uses of std::__relocate_a are guarded by checks that will
support sensible code (i.e. code not using custom allocators that fool
the new checks).

PR libstdc++/89130
* include/bits/alloc_traits.h (__is_copy_insertable_impl): Rename to
__is_alloc_insertable_impl. Replace single type member with two
members, one for each of copy and move insertable.
(__is_move_insertable): New trait for internal use.
* include/bits/stl_vector.h (vector::_S_nothrow_relocate(true_type))
(vector::_S_nothrow_relocate(true_type)): New functions to
conditionally check if __relocate_a can throw.
(vector::_S_use_relocate()): Dispatch to _S_nothrow_relocate based
on __is_move_insertable.
(vector::_S_do_relocate): New overloaded functions to conditionally
call __relocate_a.
(vector::_S_relocate): New function that dispatches to _S_do_relocate
based on _S_use_relocate.
* include/bits/vector.tcc (vector::reserve, vector::_M_realloc_insert)
(vector::_M_default_append): Call _S_relocate instead of __relocate_a.
* testsuite/23_containers/vector/modifiers/push_back/89130.cc: New.

From-SVN: r268537

libstdc++-v3/ChangeLog
libstdc++-v3/include/bits/alloc_traits.h
libstdc++-v3/include/bits/stl_vector.h
libstdc++-v3/include/bits/vector.tcc
libstdc++-v3/testsuite/23_containers/vector/modifiers/push_back/89130.cc [new file with mode: 0644]

index ff847f86d6a56cd1af2e8441754c2ad98995177b..5a94e8027de2bac2af22690571f3907a45a6c9f2 100644 (file)
@@ -1,5 +1,23 @@
 2019-02-05  Jonathan Wakely  <jwakely@redhat.com>
 
+       PR libstdc++/89130
+       * include/bits/alloc_traits.h (__is_copy_insertable_impl): Rename to
+       __is_alloc_insertable_impl. Replace single type member with two
+       members, one for each of copy and move insertable.
+       (__is_move_insertable): New trait for internal use.
+       * include/bits/stl_vector.h (vector::_S_nothrow_relocate(true_type))
+       (vector::_S_nothrow_relocate(true_type)): New functions to
+       conditionally check if __relocate_a can throw.
+       (vector::_S_use_relocate()): Dispatch to _S_nothrow_relocate based
+       on __is_move_insertable.
+       (vector::_S_do_relocate): New overloaded functions to conditionally
+       call __relocate_a.
+       (vector::_S_relocate): New function that dispatches to _S_do_relocate
+       based on _S_use_relocate.
+       * include/bits/vector.tcc (vector::reserve, vector::_M_realloc_insert)
+       (vector::_M_default_append): Call _S_relocate instead of __relocate_a.
+       * testsuite/23_containers/vector/modifiers/push_back/89130.cc: New.
+
        PR libstdc++/89090
        * include/bits/stl_uninitialized.h (__relocate_a_1): Make unused
        parameter unnamed. Add message to static assertion.
index ed61ce845f83b82a01159ce90ba8a9cbae4401c8..3b0c16fbf64e5bca974484a84c94272bbcff8c24 100644 (file)
@@ -577,14 +577,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     }
 
   template<typename _Alloc>
-    class __is_copy_insertable_impl
+    class __is_alloc_insertable_impl
     {
-      typedef allocator_traits<_Alloc> _Traits;
+      using _Traits = allocator_traits<_Alloc>;
+      using value_type = typename _Traits::value_type;
 
-      template<typename _Up, typename
+      template<typename _Up, typename _Tp = __remove_cvref_t<_Up>,
+              typename
               = decltype(_Traits::construct(std::declval<_Alloc&>(),
-                                            std::declval<_Up*>(),
-                                            std::declval<const _Up&>()))>
+                                            std::declval<_Tp*>(),
+                                            std::declval<_Up>()))>
        static true_type
        _M_select(int);
 
@@ -593,13 +595,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        _M_select(...);
 
     public:
-      typedef decltype(_M_select<typename _Alloc::value_type>(0)) type;
+      using copy = decltype(_M_select<const value_type&>(0));
+      using move = decltype(_M_select<value_type>(0));
     };
 
   // true if _Alloc::value_type is CopyInsertable into containers using _Alloc
   template<typename _Alloc>
     struct __is_copy_insertable
-    : __is_copy_insertable_impl<_Alloc>::type
+    : __is_alloc_insertable_impl<_Alloc>::copy
     { };
 
   // std::allocator<_Tp> just requires CopyConstructible
@@ -608,6 +611,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     : is_copy_constructible<_Tp>
     { };
 
+  // true if _Alloc::value_type is MoveInsertable into containers using _Alloc
+  template<typename _Alloc>
+    struct __is_move_insertable
+    : __is_alloc_insertable_impl<_Alloc>::move
+    { };
+
+  // std::allocator<_Tp> just requires MoveConstructible
+  template<typename _Tp>
+    struct __is_move_insertable<allocator<_Tp>>
+    : is_move_constructible<_Tp>
+    { };
+
   // Trait to detect Allocator-like types.
   template<typename _Alloc, typename = void>
     struct __is_allocator : false_type { };
index 43debda54f1b40075d1616f53093e484f4d191da..10bf4fac62ed6e5c23fb9c2663f372082600bfe5 100644 (file)
@@ -425,14 +425,47 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
     private:
 #if __cplusplus >= 201103L
       static constexpr bool
-      _S_use_relocate()
+      _S_nothrow_relocate(true_type)
       {
        return noexcept(std::__relocate_a(std::declval<pointer>(),
                                          std::declval<pointer>(),
                                          std::declval<pointer>(),
                                          std::declval<_Tp_alloc_type&>()));
       }
-#endif
+
+      static constexpr bool
+      _S_nothrow_relocate(false_type)
+      { return false; }
+
+      static constexpr bool
+      _S_use_relocate()
+      {
+       // Instantiating std::__relocate_a might cause an error outside the
+       // immediate context (in __relocate_object_a's noexcept-specifier),
+       // so only do it if we know the type can be move-inserted into *this.
+       return _S_nothrow_relocate(__is_move_insertable<_Tp_alloc_type>{});
+      }
+
+      static pointer
+      _S_do_relocate(pointer __first, pointer __last, pointer __result,
+                    _Tp_alloc_type& __alloc, true_type) noexcept
+      {
+       return std::__relocate_a(__first, __last, __result, __alloc);
+      }
+
+      static pointer
+      _S_do_relocate(pointer, pointer, pointer __result,
+                    _Tp_alloc_type&, false_type) noexcept
+      { return __result; }
+
+      static pointer
+      _S_relocate(pointer __first, pointer __last, pointer __result,
+                 _Tp_alloc_type& __alloc) noexcept
+      {
+       using __do_it = __bool_constant<_S_use_relocate()>;
+       return _S_do_relocate(__first, __last, __result, __alloc, __do_it{});
+      }
+#endif // C++11
 
     protected:
       using _Base::_M_allocate;
index 54c09774b15e7673f696e037ba224d651dbcc3b5..497d9f72247e0257dd02a3b2e0582300ef9274cc 100644 (file)
@@ -76,9 +76,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
          if _GLIBCXX17_CONSTEXPR (_S_use_relocate())
            {
              __tmp = this->_M_allocate(__n);
-             std::__relocate_a(this->_M_impl._M_start,
-                               this->_M_impl._M_finish,
-                               __tmp, _M_get_Tp_allocator());
+             _S_relocate(this->_M_impl._M_start, this->_M_impl._M_finish,
+                         __tmp, _M_get_Tp_allocator());
            }
          else
 #endif
@@ -459,17 +458,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 #if __cplusplus >= 201103L
          if _GLIBCXX17_CONSTEXPR (_S_use_relocate())
            {
-             __new_finish
-               = std::__relocate_a
-               (__old_start, __position.base(),
-                __new_start, _M_get_Tp_allocator());
+             __new_finish = _S_relocate(__old_start, __position.base(),
+                                        __new_start, _M_get_Tp_allocator());
 
              ++__new_finish;
 
-             __new_finish
-               = std::__relocate_a
-               (__position.base(), __old_finish,
-                __new_finish, _M_get_Tp_allocator());
+             __new_finish = _S_relocate(__position.base(), __old_finish,
+                                        __new_finish, _M_get_Tp_allocator());
            }
          else
 #endif
@@ -650,9 +645,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
                      _M_deallocate(__new_start, __len);
                      __throw_exception_again;
                    }
-                 std::__relocate_a(this->_M_impl._M_start,
-                                   this->_M_impl._M_finish,
-                                   __new_start, _M_get_Tp_allocator());
+                 _S_relocate(this->_M_impl._M_start, this->_M_impl._M_finish,
+                             __new_start, _M_get_Tp_allocator());
                }
              else
                {
diff --git a/libstdc++-v3/testsuite/23_containers/vector/modifiers/push_back/89130.cc b/libstdc++-v3/testsuite/23_containers/vector/modifiers/push_back/89130.cc
new file mode 100644 (file)
index 0000000..54b3f53
--- /dev/null
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 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-options "-std=gnu++2a" }
+// { dg-do compile { target c++2a } }
+
+#include <vector>
+
+struct T
+{
+  T() { }
+  T(const T&) { }
+  T(T&&) = delete;  // this means T is not MoveInsertable into std::vector<T>
+};
+
+void f()
+{
+  const T val;
+  std::vector<T> x;
+  // push_back(const T&) only requires T is CopyInsertable into std::vector<T>:
+  x.push_back(val);
+}
+
+template<typename U>
+struct Alloc
+{
+  using value_type = U;
+  Alloc() = default;
+  Alloc(const Alloc&) = default;
+  template<typename U2>
+    Alloc(const Alloc<U2>&) { }
+
+  U* allocate(unsigned n) { return std::allocator<U>().allocate(n); }
+  void deallocate(U* p, unsigned n) { std::allocator<U>().deallocate(p, n); }
+
+  void construct(Alloc*, U* p, U&& u)
+  {
+    // construct from const lvalue instead of rvalue:
+    ::new(p) U(const_cast<const U&>(u));
+  }
+};
+
+void g()
+{
+  const T val;
+  std::vector<T, Alloc<T>> x;
+  // push_back(const T&) only requires T is CopyInsertable into std::vector<T>:
+  x.push_back(val);
+}