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:
Jonathan Wakely 2019-02-05 14:45:00 +00:00 committed by Jonathan Wakely
parent 2781287255
commit 258bd1d63a
5 changed files with 146 additions and 23 deletions

View 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.

View 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 { };

View 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;

View 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
{

View File

@ -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);
}