https://gcc.gnu.org/g:afc9351ebbac1ed55d7c472af817b172bdb662e6
commit r15-5212-gafc9351ebbac1ed55d7c472af817b172bdb662e6 Author: Jonathan Wakely <jwak...@redhat.com> Date: Fri Nov 1 12:49:53 2024 +0000 libstdc++: Allow unordered_set assignment to assign to existing nodes Currently the _ReuseOrAllocNode::operator(Args&&...) function always destroys the value stored in recycled nodes and constructs a new value. The _ReuseOrAllocNode type is only ever used for implementing assignment, either from another unordered container of the same type, or from std::initializer_list<value_type>. Consequently, the parameter pack Args only ever consists of a single parameter or type const value_type& or value_type. We can replace the variadic parameter pack with a single forwarding reference parameter, and when the value_type is assignable from that type we can use assignment instead of destroying the existing value and then constructing a new one. Using assignment is typically only possible for sets, because for maps the value_type is std::pair<const key_type, mapped_type> and in most cases std::is_assignable_v<const key_type&, const key_type&> is false. libstdc++-v3/ChangeLog: * include/bits/hashtable_policy.h (_ReuseOrAllocNode::operator()): Replace parameter pack with a single parameter. Assign to existing value when possible. * testsuite/23_containers/unordered_multiset/allocator/move_assign.cc: Adjust expected count of operations. * testsuite/23_containers/unordered_set/allocator/move_assign.cc: Likewise. Reviewed-by: François Dumont <fdum...@gcc.gnu.org> Diff: --- libstdc++-v3/include/bits/hashtable_policy.h | 37 +++++++++++++++------- .../unordered_multiset/allocator/move_assign.cc | 5 +-- .../unordered_set/allocator/move_assign.cc | 10 +++--- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/libstdc++-v3/include/bits/hashtable_policy.h b/libstdc++-v3/include/bits/hashtable_policy.h index b5f837e60619..7a3c66c37fd6 100644 --- a/libstdc++-v3/include/bits/hashtable_policy.h +++ b/libstdc++-v3/include/bits/hashtable_policy.h @@ -172,24 +172,39 @@ namespace __detail ~_ReuseOrAllocNode() { _M_h._M_deallocate_nodes(_M_nodes); } - template<typename... _Args> +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr + template<typename _Arg> __node_ptr - operator()(_Args&&... __args) + operator()(_Arg&& __arg) { if (!_M_nodes) - return _M_h._M_allocate_node(std::forward<_Args>(__args)...); + return _M_h._M_allocate_node(std::forward<_Arg>(__arg)); + + using value_type = typename _NodeAlloc::value_type::value_type; __node_ptr __node = _M_nodes; - _M_nodes = _M_nodes->_M_next(); - __node->_M_nxt = nullptr; - auto& __a = _M_h._M_node_allocator(); - __node_alloc_traits::destroy(__a, __node->_M_valptr()); - _NodePtrGuard<__hashtable_alloc, __node_ptr> __guard { _M_h, __node }; - __node_alloc_traits::construct(__a, __node->_M_valptr(), - std::forward<_Args>(__args)...); - __guard._M_ptr = nullptr; + if constexpr (is_assignable<value_type&, _Arg>::value) + { + __node->_M_v() = std::forward<_Arg>(__arg); + _M_nodes = _M_nodes->_M_next(); + __node->_M_nxt = nullptr; + } + else + { + _M_nodes = _M_nodes->_M_next(); + __node->_M_nxt = nullptr; + auto& __a = _M_h._M_node_allocator(); + __node_alloc_traits::destroy(__a, __node->_M_valptr()); + _NodePtrGuard<__hashtable_alloc, __node_ptr> + __guard{ _M_h, __node }; + __node_alloc_traits::construct(__a, __node->_M_valptr(), + std::forward<_Arg>(__arg)); + __guard._M_ptr = nullptr; + } return __node; } +#pragma GCC diagnostic pop private: __node_ptr _M_nodes; diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/allocator/move_assign.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/allocator/move_assign.cc index 50608ec443ff..6d00354902ee 100644 --- a/libstdc++-v3/testsuite/23_containers/unordered_multiset/allocator/move_assign.cc +++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/allocator/move_assign.cc @@ -46,8 +46,9 @@ void test01() VERIFY( 1 == v1.get_allocator().get_personality() ); VERIFY( 2 == v2.get_allocator().get_personality() ); - VERIFY( counter_type::move_count == 1 ); - VERIFY( counter_type::destructor_count == 2 ); + VERIFY( counter_type::move_count == 0 ); + // 1 element in v1 destroyed. + VERIFY( counter_type::destructor_count == 1 ); } void test02() diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/allocator/move_assign.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/allocator/move_assign.cc index 677ea67d0ea7..6be70022705a 100644 --- a/libstdc++-v3/testsuite/23_containers/unordered_set/allocator/move_assign.cc +++ b/libstdc++-v3/testsuite/23_containers/unordered_set/allocator/move_assign.cc @@ -52,8 +52,9 @@ void test01() VERIFY( 1 == v1.get_allocator().get_personality() ); VERIFY( 2 == v2.get_allocator().get_personality() ); - VERIFY( counter_type::move_count == 1 ); - VERIFY( counter_type::destructor_count == 2 ); + VERIFY( counter_type::move_count == 0 ); + // 1 element in v1 destroyed. + VERIFY( counter_type::destructor_count == 1 ); } // Check there's nothing left allocated or constructed. @@ -130,8 +131,9 @@ void test03() VERIFY( 1 == v1.get_allocator().get_personality() ); VERIFY( 2 == v2.get_allocator().get_personality() ); - VERIFY( counter_type::move_count == 1 ); - VERIFY( counter_type::destructor_count == i + 1 ); + VERIFY( counter_type::move_count == 0 ); + // (i - 1) elements in v2 destroyed, and 1 element in v1 destroyed. + VERIFY( counter_type::destructor_count == i ); } // Check there's nothing left allocated or constructed.