[PATCH v2] libstdc++: Do not require assignment for vector::resize(n, v) [PR90192]
Jonathan Wakely
jwakely@redhat.com
Tue Aug 26 15:47:22 GMT 2025
On Tue, 26 Aug 2025 at 16:29, Tomasz Kamiński <tkaminsk@redhat.com> wrote:
>
> This patch introduces a new function, _M_fill_append, which is invoked when
> copies of the same value are appended to the end of a vector. Unlike
> _M_fill_insert(end(), n, v), _M_fill_append never permute elements in place,
> so it does not require:
> * vector element type to be assignable;
> * a copy of the inserted value, in the case where it points to an
> element of the vector.
>
> vector::resize(n, v) now uses _M_fill_append, fixing the non-conformance where
> element types were required to be assignable.
>
> In addition, _M_fill_insert(end(), n, v) now delegates to _M_fill_append, which
> eliminates an unnecessary copy of v when the existing capacity is used.
>
> PR libstdc++/90192
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/stl_vector.h (vector<T>::_M_fill_append): Declare.
> (vector<T>::fill): Use _M_fill_append instead of _M_fill_insert.
> * include/bits/vector.tcc (vector<T>::_M_fill_append): Define
> (vector<T>::_M_fill_insert): Delegate to _M_fill_append when
> elements are appended.
> * testsuite/23_containers/vector/modifiers/moveable.cc: Updated
> copycount for inserting at the end (appending).
> * testsuite/23_containers/vector/modifiers/resize.cc: New test.
> * testsuite/backward/hash_set/check_construct_destroy.cc: Updated
> copycount, the hash_set constructor uses insert to fill buckets
> with nullptrs.
> ---
> v2 modifies additional test case, that I must have missed.
> hash_set uses insert to append elements, which no longer creates copy.
>
> OK for trunk?
OK
>
> libstdc++-v3/include/bits/stl_vector.h | 9 ++-
> libstdc++-v3/include/bits/vector.tcc | 60 +++++++++++++++-
> .../vector/modifiers/moveable.cc | 6 +-
> .../23_containers/vector/modifiers/resize.cc | 69 +++++++++++++++++++
> .../hash_set/check_construct_destroy.cc | 25 +++----
> 5 files changed, 148 insertions(+), 21 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/23_containers/vector/modifiers/resize.cc
>
> diff --git a/libstdc++-v3/include/bits/stl_vector.h b/libstdc++-v3/include/bits/stl_vector.h
> index f2c1bce1e38..7625333c9ad 100644
> --- a/libstdc++-v3/include/bits/stl_vector.h
> +++ b/libstdc++-v3/include/bits/stl_vector.h
> @@ -1154,7 +1154,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> resize(size_type __new_size, const value_type& __x)
> {
> if (__new_size > size())
> - _M_fill_insert(end(), __new_size - size(), __x);
> + _M_fill_append(__new_size - size(), __x);
> else if (__new_size < size())
> _M_erase_at_end(this->_M_impl._M_start + __new_size);
> }
> @@ -1175,7 +1175,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> resize(size_type __new_size, value_type __x = value_type())
> {
> if (__new_size > size())
> - _M_fill_insert(end(), __new_size - size(), __x);
> + _M_fill_append(__new_size - size(), __x);
> else if (__new_size < size())
> _M_erase_at_end(this->_M_impl._M_start + __new_size);
> }
> @@ -2088,6 +2088,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> void
> _M_fill_insert(iterator __pos, size_type __n, const value_type& __x);
>
> + // Called by resize(n,x), and the _M_fill_insert(end(), n, x)
> + _GLIBCXX20_CONSTEXPR
> + void
> + _M_fill_append(size_type __n, const value_type& __x);
> +
> #if __cplusplus >= 201103L
> // Called by resize(n).
> _GLIBCXX20_CONSTEXPR
> diff --git a/libstdc++-v3/include/bits/vector.tcc b/libstdc++-v3/include/bits/vector.tcc
> index dd3d3c6fd65..642edb5740e 100644
> --- a/libstdc++-v3/include/bits/vector.tcc
> +++ b/libstdc++-v3/include/bits/vector.tcc
> @@ -664,8 +664,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> {
> if (__n != 0)
> {
> - if (size_type(this->_M_impl._M_end_of_storage
> - - this->_M_impl._M_finish) >= __n)
> + if (__position.base() == this->_M_impl._M_finish)
> + _M_fill_append(__n, __x);
> + else if (size_type(this->_M_impl._M_end_of_storage
> + - this->_M_impl._M_finish) >= __n)
> {
> #if __cplusplus < 201103L
> value_type __x_copy = __x;
> @@ -760,6 +762,60 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
> }
> }
>
> + template<typename _Tp, typename _Alloc>
> + _GLIBCXX20_CONSTEXPR
> + void
> + vector<_Tp, _Alloc>::
> + _M_fill_append(size_type __n, const value_type& __x)
> + {
> + if (size_type(this->_M_impl._M_end_of_storage
> + - this->_M_impl._M_finish) >= __n)
> + {
> + _GLIBCXX_ASAN_ANNOTATE_GROW(__n);
> + this->_M_impl._M_finish =
> + std::__uninitialized_fill_n_a(this->_M_impl._M_finish, __n, __x,
> + _M_get_Tp_allocator());
> + _GLIBCXX_ASAN_ANNOTATE_GREW(__n);
> + }
> + else
> + {
> + // Make local copies of these members because the compiler thinks
> + // the allocator can alter them if 'this' is globally reachable.
> + pointer __old_start = this->_M_impl._M_start;
> + pointer __old_finish = this->_M_impl._M_finish;
> + const size_type __old_size = __old_finish - __old_start;
> +
> + const size_type __len =
> + _M_check_len(__n, "vector::_M_fill_append");
> + pointer __new_start(this->_M_allocate(__len));
> + pointer __new_finish(__new_start + __old_size);
> + __try
> + {
> + // See _M_realloc_insert above.
> + __new_finish = std::__uninitialized_fill_n_a(
> + __new_finish, __n, __x,
> + _M_get_Tp_allocator());
> + std::__uninitialized_move_if_noexcept_a(
> + __old_start, __old_finish, __new_start,
> + _M_get_Tp_allocator());
> + }
> + __catch(...)
> + {
> + std::_Destroy(__new_start + __old_size, __new_finish,
> + _M_get_Tp_allocator());
> + _M_deallocate(__new_start, __len);
> + __throw_exception_again;
> + }
> + std::_Destroy(__old_start, __old_finish, _M_get_Tp_allocator());
> + _GLIBCXX_ASAN_ANNOTATE_REINIT;
> + _M_deallocate(__old_start,
> + this->_M_impl._M_end_of_storage - __old_start);
> + this->_M_impl._M_start = __new_start;
> + this->_M_impl._M_finish = __new_finish;
> + this->_M_impl._M_end_of_storage = __new_start + __len;
> + }
> + }
> +
> #if __cplusplus >= 201103L
> #pragma GCC diagnostic push
> #pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
> diff --git a/libstdc++-v3/testsuite/23_containers/vector/modifiers/moveable.cc b/libstdc++-v3/testsuite/23_containers/vector/modifiers/moveable.cc
> index 8d0f9ae9bd1..343a298823e 100644
> --- a/libstdc++-v3/testsuite/23_containers/vector/modifiers/moveable.cc
> +++ b/libstdc++-v3/testsuite/23_containers/vector/modifiers/moveable.cc
> @@ -109,9 +109,11 @@ test05()
> // when it doesn't reallocate the buffer.
> VERIFY(copycounter::copycount == 20 + 1);
> a.insert(a.end(), 50, c);
> - VERIFY(copycounter::copycount == 70 + 2);
> + // expect when inserting at the end (appending), where existing
> + // elements are not modified
> + VERIFY(copycounter::copycount == 70 + 1);
> a.insert(a.begin() + 50, 100, c);
> - VERIFY(copycounter::copycount == 170 + 3);
> + VERIFY(copycounter::copycount == 170 + 2);
> }
>
>
> diff --git a/libstdc++-v3/testsuite/23_containers/vector/modifiers/resize.cc b/libstdc++-v3/testsuite/23_containers/vector/modifiers/resize.cc
> new file mode 100644
> index 00000000000..026b0f78b50
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/23_containers/vector/modifiers/resize.cc
> @@ -0,0 +1,69 @@
> +// { dg-do run }
> +
> +#include <vector>
> +#include <testsuite_hooks.h>
> +
> +struct NoAssign
> +{
> + NoAssign(int p) : val(p) {}
> + const int val;
> +};
> +
> +struct PrivateAssign
> +{
> + PrivateAssign(int p) : val(p) {}
> + PrivateAssign(const PrivateAssign& rhs) : val(rhs.val) {}
> +
> + int val;
> +
> +private:
> + PrivateAssign& operator=(const PrivateAssign&);
> +};
> +
> +#if __cplusplus >= 201102L
> +struct DeletedAssign
> +{
> + DeletedAssign(int p) : val(p) {}
> + DeletedAssign(const DeletedAssign& rhs) : val(rhs.val) {}
> +
> + DeletedAssign& operator=(const DeletedAssign&) = delete;
> +
> + int val;
> +};
> +#endif
> +
> +template<typename T>
> +void
> +testPR90129()
> +{
> + std::vector<T> v;
> + v.resize(5, T(5));
> + VERIFY( v.size() == 5 );
> + VERIFY( v.front().val == 5 );
> + VERIFY( v.back().val == 5 );
> +
> + v.resize(10, T(10));
> + VERIFY( v.size() == 10 );
> + VERIFY( v.front().val == 5 );
> + VERIFY( v.back().val == 10 );
> +
> + v.resize(7, T(7));
> + VERIFY( v.size() == 7 );
> + VERIFY( v.front().val == 5 );
> + VERIFY( v.back().val == 10 );
> +
> + v.resize(3, T(3));
> + VERIFY( v.size() == 3 );
> + VERIFY( v.front().val == 5 );
> + VERIFY( v.back().val == 5 );
> +}
> +
> +int main()
> +{
> + testPR90129<NoAssign>();
> + testPR90129<PrivateAssign>();
> +#if __cplusplus >= 201102L
> + testPR90129<DeletedAssign>();
> +#endif
> + return 0;
> +}
> diff --git a/libstdc++-v3/testsuite/backward/hash_set/check_construct_destroy.cc b/libstdc++-v3/testsuite/backward/hash_set/check_construct_destroy.cc
> index 042de4ebdbe..aca296d1daf 100644
> --- a/libstdc++-v3/testsuite/backward/hash_set/check_construct_destroy.cc
> +++ b/libstdc++-v3/testsuite/backward/hash_set/check_construct_destroy.cc
> @@ -39,50 +39,45 @@ int main()
>
> int buckets;
>
> - // For C++11 and later add 1 to all counts, because the std::vector used
> - // internally by the hashtable creates and destroys a temporary object
> - // using its allocator.
> - const int extra = __cplusplus >= 201102L ? 1 : 0;
> -
> tracker_allocator_counter::reset();
> {
> Container c;
> buckets = c.bucket_count();
> - ok = check_construct_destroy("empty container", buckets+extra, extra) && ok;
> + ok = check_construct_destroy("empty container", buckets, 0) && ok;
> }
> - ok = check_construct_destroy("empty container", buckets+extra, buckets+extra) && ok;
> + ok = check_construct_destroy("empty container", buckets, buckets) && ok;
>
>
> tracker_allocator_counter::reset();
> {
> Container c(arr10, arr10 + 10);
> - ok = check_construct_destroy("Construct from range", buckets+10+extra, extra) && ok;
> + ok = check_construct_destroy("Construct from range", buckets+10, 0) && ok;
> }
> - ok = check_construct_destroy("Construct from range", buckets+10+extra, buckets+10+extra) && ok;
> + ok = check_construct_destroy("Construct from range", buckets+10, buckets+10) && ok;
>
> tracker_allocator_counter::reset();
> {
> Container c(arr10, arr10 + 10);
> c.insert(arr10a[0]);
> - ok = check_construct_destroy("Insert element", buckets+11+extra, extra) && ok;
> + ok = check_construct_destroy("Insert element", buckets+11, 0) && ok;
> }
> - ok = check_construct_destroy("Insert element", buckets+11+extra, buckets+11+extra) && ok;
> + ok = check_construct_destroy("Insert element", buckets+11, buckets+11) && ok;
>
> tracker_allocator_counter::reset();
> {
> Container c(arr10, arr10 + 10);
> c.insert(arr10a, arr10a+3);
> - ok = check_construct_destroy("Insert short range", buckets+13+extra, extra) && ok;
> + ok = check_construct_destroy("Insert short range", buckets+13, 0) && ok;
> }
> - ok = check_construct_destroy("Insert short range", buckets+13+extra, buckets+13+extra) && ok;
> + ok = check_construct_destroy("Insert short range", buckets+13, buckets+13) && ok;
>
> tracker_allocator_counter::reset();
> {
> Container c(arr10, arr10 + 10);
> c.insert(arr10a, arr10a+10);
> - ok = check_construct_destroy("Insert long range", buckets+20+extra, extra) && ok;
> + ok = check_construct_destroy("Insert long range", buckets+20, 0) && ok;
> }
> - ok = check_construct_destroy("Insert long range", buckets+20+extra, buckets+20+extra) && ok;
> + ok = check_construct_destroy("Insert long range", buckets+20, buckets+20) && ok;
>
> return ok ? 0 : 1;
> }
> --
> 2.50.1
>
More information about the Libstdc++
mailing list