PR libstdc++/89130 restore support for non-MoveConstructible types
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
This commit is contained in:
parent
2781287255
commit
258bd1d63a
|
@ -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.
|
||||
|
|
|
@ -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 { };
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue