https://gcc.gnu.org/g:390f3a690cf5c6bc8f7290484cafa6617c1757d4
commit r16-3399-g390f3a690cf5c6bc8f7290484cafa6617c1757d4 Author: Tomasz Kamiński <tkami...@redhat.com> Date: Mon Aug 25 13:15:35 2025 +0200 libstdc++: Do not require assignment for vector::resize(n, v) [PR90192] 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. Reviewed-by: Jonathan Wakely <jwak...@redhat.com> Signed-off-by: Tomasz Kamiński <tkami...@redhat.com> Diff: --- libstdc++-v3/include/bits/stl_vector.h | 9 ++- libstdc++-v3/include/bits/vector.tcc | 60 ++++++++++++++++++- .../23_containers/vector/modifiers/moveable.cc | 6 +- .../23_containers/vector/modifiers/resize.cc | 69 ++++++++++++++++++++++ .../backward/hash_set/check_construct_destroy.cc | 25 ++++---- 5 files changed, 148 insertions(+), 21 deletions(-) diff --git a/libstdc++-v3/include/bits/stl_vector.h b/libstdc++-v3/include/bits/stl_vector.h index f2c1bce1e386..7625333c9ad8 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 dd3d3c6fd65e..642edb5740ed 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 8d0f9ae9bd1e..343a298823e3 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 000000000000..026b0f78b50f --- /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 042de4ebdbe3..aca296d1daf5 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; }