From 5d9de6006c5b72bf8908d7d64ebf0c882bb790e9 Mon Sep 17 00:00:00 2001 From: Nikolas Klauser Date: Sat, 2 Aug 2025 08:06:41 +0200 Subject: [PATCH] [libc++] Optimize __hash_table copy constructors and assignment --- libcxx/docs/ReleaseNotes/22.rst | 2 + libcxx/include/__hash_table | 135 +++- libcxx/include/unordered_map | 84 +-- libcxx/include/unordered_set | 47 +- .../unord.map.cnstr/assign_copy.pass.cpp | 538 ++++++++++++---- .../unord.map/unord.map.cnstr/copy.pass.cpp | 234 ++++--- .../unord.map.cnstr/copy_alloc.pass.cpp | 200 +++--- .../unord.multimap.cnstr/assign_copy.pass.cpp | 586 ++++++++++++++---- .../unord.multimap.cnstr/copy.pass.cpp | 270 ++++---- .../unord.multimap.cnstr/copy_alloc.pass.cpp | 237 ++++--- .../unord.multiset.cnstr/assign_copy.pass.cpp | 500 ++++++++++++--- .../unord.multiset.cnstr/copy.pass.cpp | 172 +++-- .../unord.multiset.cnstr/copy_alloc.pass.cpp | 137 ++-- .../unord.set.cnstr/assign_copy.pass.cpp | 505 ++++++++++++--- .../unord.set/unord.set.cnstr/copy.pass.cpp | 178 ++++-- .../unord.set.cnstr/copy_alloc.pass.cpp | 136 ++-- 16 files changed, 2708 insertions(+), 1253 deletions(-) diff --git a/libcxx/docs/ReleaseNotes/22.rst b/libcxx/docs/ReleaseNotes/22.rst index 8b8dce5083149..0701618044a3b 100644 --- a/libcxx/docs/ReleaseNotes/22.rst +++ b/libcxx/docs/ReleaseNotes/22.rst @@ -45,6 +45,8 @@ Improvements and New Features - The performance of ``map::map(const map&)`` has been improved up to 2.3x - The performance of ``map::operator=(const map&)`` has been improved by up to 11x +- The performance of ``unordered_set::unordered_set(const unordered_set&)`` has been improved by up to 3.3x. +- The performance of ``unordered_set::operator=(const unordered_set&)`` has been improved by up to 5x. Deprecations and Removals ------------------------- diff --git a/libcxx/include/__hash_table b/libcxx/include/__hash_table index dacc152030e14..996ec9fa31ac3 100644 --- a/libcxx/include/__hash_table +++ b/libcxx/include/__hash_table @@ -10,6 +10,7 @@ #ifndef _LIBCPP___HASH_TABLE #define _LIBCPP___HASH_TABLE +#include <__algorithm/fill_n.h> #include <__algorithm/max.h> #include <__algorithm/min.h> #include <__assert> @@ -700,6 +701,38 @@ private: _LIBCPP_HIDE_FROM_ABI size_type& size() _NOEXCEPT { return __size_; } + _LIBCPP_HIDE_FROM_ABI void + __copy_construct(__next_pointer __other_iter, __next_pointer __own_iter, size_t __current_chash) { + auto __bucket_count = bucket_count(); + + for (; __other_iter; __other_iter = __other_iter->__next_) { + __node_holder __new_node = __construct_node_hash(__other_iter->__hash(), __other_iter->__upcast()->__get_value()); + + size_t __new_chash = std::__constrain_hash(__new_node->__hash(), __bucket_count); + if (__new_chash != __current_chash) { + __bucket_list_[__new_chash] = __own_iter; + __current_chash = __new_chash; + } + + __own_iter->__next_ = static_cast<__next_pointer>(__new_node.release()); + __own_iter = __own_iter->__next_; + } + } + + _LIBCPP_HIDE_FROM_ABI void __copy_construct(__next_pointer __other_iter) { + __next_pointer __own_iter = __first_node_.__ptr(); + { + __node_holder __new_node = __construct_node_hash(__other_iter->__hash(), __other_iter->__upcast()->__get_value()); + __own_iter->__next_ = static_cast<__next_pointer>(__new_node.release()); + } + + size_t __current_chash = std::__constrain_hash(__own_iter->__next_->__hash(), bucket_count()); + __bucket_list_[__current_chash] = __own_iter; + __other_iter = __other_iter->__next_; + __own_iter = __own_iter->__next_; + __copy_construct(__other_iter, __own_iter, __current_chash); + } + public: _LIBCPP_HIDE_FROM_ABI size_type size() const _NOEXCEPT { return __size_; } @@ -1048,16 +1081,29 @@ __hash_table<_Tp, _Hash, _Equal, _Alloc>::__hash_table(const allocator_type& __a __max_load_factor_(1.0f) {} template -__hash_table<_Tp, _Hash, _Equal, _Alloc>::__hash_table(const __hash_table& __u) +__hash_table<_Tp, _Hash, _Equal, _Alloc>::__hash_table(const __hash_table& __other) : __bucket_list_(nullptr, - __bucket_list_deleter(allocator_traits<__pointer_allocator>::select_on_container_copy_construction( - __u.__bucket_list_.get_deleter().__alloc()), + __bucket_list_deleter(__pointer_alloc_traits::select_on_container_copy_construction( + __other.__bucket_list_.get_deleter().__alloc()), 0)), - __node_alloc_(allocator_traits<__node_allocator>::select_on_container_copy_construction(__u.__node_alloc())), + __node_alloc_(__node_traits::select_on_container_copy_construction(__other.__node_alloc())), __size_(0), - __hasher_(__u.hash_function()), - __max_load_factor_(__u.__max_load_factor_), - __key_eq_(__u.__key_eq_) {} + __hasher_(__other.hash_function()), + __max_load_factor_(__other.__max_load_factor_), + __key_eq_(__other.__key_eq_) { + if (__other.size() == 0) + return; + + auto& __bucket_list_del = __bucket_list_.get_deleter(); + auto __bucket_count = __other.bucket_count(); + __bucket_list_.reset(__pointer_alloc_traits::allocate(__bucket_list_del.__alloc(), __bucket_count)); + __bucket_list_del.size() = __bucket_count; + + std::fill_n(__bucket_list_.get(), __bucket_count, nullptr); + + __copy_construct(__other.__first_node_.__next_); + __size_ = __other.size(); +} template __hash_table<_Tp, _Hash, _Equal, _Alloc>::__hash_table(const __hash_table& __u, const allocator_type& __a) @@ -1131,14 +1177,75 @@ void __hash_table<_Tp, _Hash, _Equal, _Alloc>::__copy_assign_alloc(const __hash_ } template -__hash_table<_Tp, _Hash, _Equal, _Alloc>& __hash_table<_Tp, _Hash, _Equal, _Alloc>::operator=(const __hash_table& __u) { - if (this != std::addressof(__u)) { - __copy_assign_alloc(__u); - hash_function() = __u.hash_function(); - key_eq() = __u.key_eq(); - max_load_factor() = __u.max_load_factor(); - __assign_multi(__u.begin(), __u.end()); +__hash_table<_Tp, _Hash, _Equal, _Alloc>& +__hash_table<_Tp, _Hash, _Equal, _Alloc>::operator=(const __hash_table& __other) { + if (this == std::addressof(__other)) + return *this; + + __copy_assign_alloc(__other); + hash_function() = __other.hash_function(); + key_eq() = __other.key_eq(); + max_load_factor() = __other.max_load_factor(); + + if (__other.size() == 0) { + clear(); + return *this; + } + + auto __bucket_count = __other.bucket_count(); + if (__bucket_count != bucket_count()) { + auto& __bucket_list_del = __bucket_list_.get_deleter(); + __bucket_list_.reset(__pointer_alloc_traits::allocate(__bucket_list_del.__alloc(), __bucket_count)); + __bucket_list_del.size() = __bucket_count; + } + std::fill_n(__bucket_list_.get(), __bucket_count, nullptr); + + if (!__first_node_.__next_) { + __copy_construct(__other.__first_node_.__next_); + __size_ = __other.size(); + return *this; } + + __next_pointer __other_iter = __other.__first_node_.__next_; + __next_pointer __own_iter = __first_node_.__ptr(); + { + __node_pointer __next = __own_iter->__next_->__upcast(); + __assign_value(__next->__get_value(), __other_iter->__upcast()->__get_value()); + __next->__hash_ = __other_iter->__hash(); + } + size_t __current_chash = std::__constrain_hash(__own_iter->__next_->__hash(), __bucket_count); + __bucket_list_[__current_chash] = __own_iter; + __other_iter = __other_iter->__next_; + __own_iter = __own_iter->__next_; + + // Go through the nodes of the incoming hash table and copy then into the destination hash table, reusing as many + // existing nodes as posssible in the destination. + while (__other_iter && __own_iter->__next_) { + __node_pointer __next = __own_iter->__next_->__upcast(); + __assign_value(__next->__get_value(), __other_iter->__upcast()->__get_value()); + __next->__hash_ = __other_iter->__hash(); + + size_t __new_chash = std::__constrain_hash(__next->__hash_, __bucket_count); + if (__new_chash != __current_chash) { + __bucket_list_[__new_chash] = __own_iter; + __current_chash = __new_chash; + } + + __other_iter = __other_iter->__next_; + __own_iter = __own_iter->__next_; + } + + // At this point we either have consumed the whole incoming hash table, or we don't have any more nodes to reuse in + // the destination. Either continue with constructing new nodes, or deallocate the left over nodes. + if (__own_iter->__next_) { + __deallocate_node(__own_iter->__next_); + __own_iter->__next_ = nullptr; + } else { + __copy_construct(__other_iter, __own_iter, __current_chash); + } + + __size_ = __other.size(); + return *this; } diff --git a/libcxx/include/unordered_map b/libcxx/include/unordered_map index 97c2c52eba337..104dc56a89fea 100644 --- a/libcxx/include/unordered_map +++ b/libcxx/include/unordered_map @@ -1046,10 +1046,11 @@ public: # endif _LIBCPP_HIDE_FROM_ABI explicit unordered_map(const allocator_type& __a); - _LIBCPP_HIDE_FROM_ABI unordered_map(const unordered_map& __u); + _LIBCPP_HIDE_FROM_ABI unordered_map(const unordered_map& __u) = default; _LIBCPP_HIDE_FROM_ABI unordered_map(const unordered_map& __u, const allocator_type& __a); # ifndef _LIBCPP_CXX03_LANG - _LIBCPP_HIDE_FROM_ABI unordered_map(unordered_map&& __u) _NOEXCEPT_(is_nothrow_move_constructible<__table>::value); + _LIBCPP_HIDE_FROM_ABI unordered_map(unordered_map&& __u) + _NOEXCEPT_(is_nothrow_move_constructible<__table>::value) = default; _LIBCPP_HIDE_FROM_ABI unordered_map(unordered_map&& __u, const allocator_type& __a); _LIBCPP_HIDE_FROM_ABI unordered_map(initializer_list __il); _LIBCPP_HIDE_FROM_ABI @@ -1099,24 +1100,10 @@ public: static_assert(sizeof(std::__diagnose_unordered_container_requirements<_Key, _Hash, _Pred>(0)), ""); } - _LIBCPP_HIDE_FROM_ABI unordered_map& operator=(const unordered_map& __u) { -# ifndef _LIBCPP_CXX03_LANG - __table_ = __u.__table_; -# else - if (this != std::addressof(__u)) { - __table_.clear(); - __table_.hash_function() = __u.__table_.hash_function(); - __table_.key_eq() = __u.__table_.key_eq(); - __table_.max_load_factor() = __u.__table_.max_load_factor(); - __table_.__copy_assign_alloc(__u.__table_); - insert(__u.begin(), __u.end()); - } -# endif - return *this; - } + _LIBCPP_HIDE_FROM_ABI unordered_map& operator=(const unordered_map& __u) = default; # ifndef _LIBCPP_CXX03_LANG _LIBCPP_HIDE_FROM_ABI unordered_map& operator=(unordered_map&& __u) - _NOEXCEPT_(is_nothrow_move_assignable<__table>::value); + _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) = default; _LIBCPP_HIDE_FROM_ABI unordered_map& operator=(initializer_list __il); # endif // _LIBCPP_CXX03_LANG @@ -1563,12 +1550,6 @@ unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( insert(__first, __last); } -template -unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(const unordered_map& __u) : __table_(__u.__table_) { - __table_.__rehash_unique(__u.bucket_count()); - insert(__u.begin(), __u.end()); -} - template unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(const unordered_map& __u, const allocator_type& __a) : __table_(__u.__table_, typename __table::allocator_type(__a)) { @@ -1578,11 +1559,6 @@ unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(const unordered_ma # ifndef _LIBCPP_CXX03_LANG -template -inline unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(unordered_map&& __u) - _NOEXCEPT_(is_nothrow_move_constructible<__table>::value) - : __table_(std::move(__u.__table_)) {} - template unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(unordered_map&& __u, const allocator_type& __a) : __table_(std::move(__u.__table_), typename __table::allocator_type(__a)) { @@ -1618,14 +1594,6 @@ unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( insert(__il.begin(), __il.end()); } -template -inline unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& -unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=(unordered_map&& __u) - _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) { - __table_ = std::move(__u.__table_); - return *this; -} - template inline unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=(initializer_list __il) { @@ -1852,11 +1820,11 @@ public: # endif _LIBCPP_HIDE_FROM_ABI explicit unordered_multimap(const allocator_type& __a); - _LIBCPP_HIDE_FROM_ABI unordered_multimap(const unordered_multimap& __u); + _LIBCPP_HIDE_FROM_ABI unordered_multimap(const unordered_multimap& __u) = default; _LIBCPP_HIDE_FROM_ABI unordered_multimap(const unordered_multimap& __u, const allocator_type& __a); # ifndef _LIBCPP_CXX03_LANG _LIBCPP_HIDE_FROM_ABI unordered_multimap(unordered_multimap&& __u) - _NOEXCEPT_(is_nothrow_move_constructible<__table>::value); + _NOEXCEPT_(is_nothrow_move_constructible<__table>::value) = default; _LIBCPP_HIDE_FROM_ABI unordered_multimap(unordered_multimap&& __u, const allocator_type& __a); _LIBCPP_HIDE_FROM_ABI unordered_multimap(initializer_list __il); _LIBCPP_HIDE_FROM_ABI unordered_multimap( @@ -1906,24 +1874,10 @@ public: static_assert(sizeof(std::__diagnose_unordered_container_requirements<_Key, _Hash, _Pred>(0)), ""); } - _LIBCPP_HIDE_FROM_ABI unordered_multimap& operator=(const unordered_multimap& __u) { -# ifndef _LIBCPP_CXX03_LANG - __table_ = __u.__table_; -# else - if (this != std::addressof(__u)) { - __table_.clear(); - __table_.hash_function() = __u.__table_.hash_function(); - __table_.key_eq() = __u.__table_.key_eq(); - __table_.max_load_factor() = __u.__table_.max_load_factor(); - __table_.__copy_assign_alloc(__u.__table_); - insert(__u.begin(), __u.end()); - } -# endif - return *this; - } + _LIBCPP_HIDE_FROM_ABI unordered_multimap& operator=(const unordered_multimap& __u) = default; # ifndef _LIBCPP_CXX03_LANG _LIBCPP_HIDE_FROM_ABI unordered_multimap& operator=(unordered_multimap&& __u) - _NOEXCEPT_(is_nothrow_move_assignable<__table>::value); + _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) = default; _LIBCPP_HIDE_FROM_ABI unordered_multimap& operator=(initializer_list __il); # endif // _LIBCPP_CXX03_LANG @@ -2315,13 +2269,6 @@ template inline unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap(const allocator_type& __a) : __table_(typename __table::allocator_type(__a)) {} -template -unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap(const unordered_multimap& __u) - : __table_(__u.__table_) { - __table_.__rehash_multi(__u.bucket_count()); - insert(__u.begin(), __u.end()); -} - template unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( const unordered_multimap& __u, const allocator_type& __a) @@ -2332,11 +2279,6 @@ unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( # ifndef _LIBCPP_CXX03_LANG -template -inline unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap(unordered_multimap&& __u) - _NOEXCEPT_(is_nothrow_move_constructible<__table>::value) - : __table_(std::move(__u.__table_)) {} - template unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( unordered_multimap&& __u, const allocator_type& __a) @@ -2373,14 +2315,6 @@ unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( insert(__il.begin(), __il.end()); } -template -inline unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& -unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=(unordered_multimap&& __u) - _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) { - __table_ = std::move(__u.__table_); - return *this; -} - template inline unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=(initializer_list __il) { diff --git a/libcxx/include/unordered_set b/libcxx/include/unordered_set index 475715db62bdb..09bd81a22eae5 100644 --- a/libcxx/include/unordered_set +++ b/libcxx/include/unordered_set @@ -703,7 +703,7 @@ public: # endif _LIBCPP_HIDE_FROM_ABI explicit unordered_set(const allocator_type& __a); - _LIBCPP_HIDE_FROM_ABI unordered_set(const unordered_set& __u); + _LIBCPP_HIDE_FROM_ABI unordered_set(const unordered_set& __u) = default; _LIBCPP_HIDE_FROM_ABI unordered_set(const unordered_set& __u, const allocator_type& __a); # ifndef _LIBCPP_CXX03_LANG _LIBCPP_HIDE_FROM_ABI unordered_set(unordered_set&& __u) _NOEXCEPT_(is_nothrow_move_constructible<__table>::value); @@ -733,13 +733,10 @@ public: static_assert(sizeof(std::__diagnose_unordered_container_requirements<_Value, _Hash, _Pred>(0)), ""); } - _LIBCPP_HIDE_FROM_ABI unordered_set& operator=(const unordered_set& __u) { - __table_ = __u.__table_; - return *this; - } + _LIBCPP_HIDE_FROM_ABI unordered_set& operator=(const unordered_set& __u) = default; # ifndef _LIBCPP_CXX03_LANG _LIBCPP_HIDE_FROM_ABI unordered_set& operator=(unordered_set&& __u) - _NOEXCEPT_(is_nothrow_move_assignable<__table>::value); + _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) = default; _LIBCPP_HIDE_FROM_ABI unordered_set& operator=(initializer_list __il); # endif // _LIBCPP_CXX03_LANG @@ -1070,12 +1067,6 @@ unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set( template inline unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set(const allocator_type& __a) : __table_(__a) {} -template -unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set(const unordered_set& __u) : __table_(__u.__table_) { - __table_.__rehash_unique(__u.bucket_count()); - insert(__u.begin(), __u.end()); -} - template unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set(const unordered_set& __u, const allocator_type& __a) : __table_(__u.__table_, __a) { @@ -1125,14 +1116,6 @@ unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set( insert(__il.begin(), __il.end()); } -template -inline unordered_set<_Value, _Hash, _Pred, _Alloc>& -unordered_set<_Value, _Hash, _Pred, _Alloc>::operator=(unordered_set&& __u) - _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) { - __table_ = std::move(__u.__table_); - return *this; -} - template inline unordered_set<_Value, _Hash, _Pred, _Alloc>& unordered_set<_Value, _Hash, _Pred, _Alloc>::operator=(initializer_list __il) { @@ -1308,7 +1291,7 @@ public: # endif _LIBCPP_HIDE_FROM_ABI explicit unordered_multiset(const allocator_type& __a); - _LIBCPP_HIDE_FROM_ABI unordered_multiset(const unordered_multiset& __u); + _LIBCPP_HIDE_FROM_ABI unordered_multiset(const unordered_multiset& __u) = default; _LIBCPP_HIDE_FROM_ABI unordered_multiset(const unordered_multiset& __u, const allocator_type& __a); # ifndef _LIBCPP_CXX03_LANG _LIBCPP_HIDE_FROM_ABI unordered_multiset(unordered_multiset&& __u) @@ -1339,13 +1322,10 @@ public: static_assert(sizeof(std::__diagnose_unordered_container_requirements<_Value, _Hash, _Pred>(0)), ""); } - _LIBCPP_HIDE_FROM_ABI unordered_multiset& operator=(const unordered_multiset& __u) { - __table_ = __u.__table_; - return *this; - } + _LIBCPP_HIDE_FROM_ABI unordered_multiset& operator=(const unordered_multiset& __u) = default; # ifndef _LIBCPP_CXX03_LANG _LIBCPP_HIDE_FROM_ABI unordered_multiset& operator=(unordered_multiset&& __u) - _NOEXCEPT_(is_nothrow_move_assignable<__table>::value); + _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) = default; _LIBCPP_HIDE_FROM_ABI unordered_multiset& operator=(initializer_list __il); # endif // _LIBCPP_CXX03_LANG @@ -1685,13 +1665,6 @@ template inline unordered_multiset<_Value, _Hash, _Pred, _Alloc>::unordered_multiset(const allocator_type& __a) : __table_(__a) {} -template -unordered_multiset<_Value, _Hash, _Pred, _Alloc>::unordered_multiset(const unordered_multiset& __u) - : __table_(__u.__table_) { - __table_.__rehash_multi(__u.bucket_count()); - insert(__u.begin(), __u.end()); -} - template unordered_multiset<_Value, _Hash, _Pred, _Alloc>::unordered_multiset( const unordered_multiset& __u, const allocator_type& __a) @@ -1743,14 +1716,6 @@ unordered_multiset<_Value, _Hash, _Pred, _Alloc>::unordered_multiset( insert(__il.begin(), __il.end()); } -template -inline unordered_multiset<_Value, _Hash, _Pred, _Alloc>& -unordered_multiset<_Value, _Hash, _Pred, _Alloc>::operator=(unordered_multiset&& __u) - _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) { - __table_ = std::move(__u.__table_); - return *this; -} - template inline unordered_multiset<_Value, _Hash, _Pred, _Alloc>& unordered_multiset<_Value, _Hash, _Pred, _Alloc>::operator=(initializer_list __il) { diff --git a/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/assign_copy.pass.cpp b/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/assign_copy.pass.cpp index 34dec07b03e08..3e4c5b1c03fcd 100644 --- a/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/assign_copy.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/assign_copy.pass.cpp @@ -14,13 +14,16 @@ // unordered_map& operator=(const unordered_map& u); +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + #include -#include -#include #include #include #include #include +#include +#include +#include #include "test_macros.h" #include "../../../test_compare.h" @@ -28,116 +31,437 @@ #include "test_allocator.h" #include "min_allocator.h" -int main(int, char**) { - { - typedef test_allocator > A; - typedef std::unordered_map, test_equal_to, A > C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A(10)); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A(4)); - c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.at(1) == "one"); - assert(c.at(2) == "two"); - assert(c.at(3) == "three"); - assert(c.at(4) == "four"); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A(4)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +class tracking_allocator { + std::vector* allocs_; + + template + friend class tracking_allocator; + +public: + using value_type = T; + using propagate_on_container_copy_assignment = std::true_type; + + tracking_allocator(std::vector& allocs) : allocs_(&allocs) {} + + template + tracking_allocator(const tracking_allocator& other) : allocs_(other.allocs_) {} + + T* allocate(std::size_t n) { + T* allocation = std::allocator().allocate(n); + allocs_->push_back(allocation); + return allocation; } - { - typedef std::unordered_map C; - typedef std::pair P; - const P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c(a, a + sizeof(a) / sizeof(a[0])); - C* p = &c; - c = *p; - assert(c.size() == 4); - assert(std::is_permutation(c.begin(), c.end(), a)); + + void deallocate(T* ptr, std::size_t n) TEST_NOEXCEPT { + auto res = std::remove(allocs_->begin(), allocs_->end(), ptr); + assert(res != allocs_->end() && "Trying to deallocate memory from different allocator?"); + allocs_->erase(res); + std::allocator().deallocate(ptr, n); } - { - typedef other_allocator > A; - typedef std::unordered_map, test_equal_to, A > C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A(10)); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A(4)); - c = c0; - assert(c.bucket_count() >= 5); - assert(c.size() == 4); - assert(c.at(1) == "one"); - assert(c.at(2) == "two"); - assert(c.at(3) == "three"); - assert(c.at(4) == "four"); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A(10)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + friend bool operator==(const tracking_allocator& lhs, const tracking_allocator& rhs) { + return lhs.allocs_ == rhs.allocs_; + } + + friend bool operator!=(const tracking_allocator& lhs, const tracking_allocator& rhs) { + return lhs.allocs_ != rhs.allocs_; + } +}; + +struct NoOp { + void operator()() {} +}; + +template +void test_alloc(const Alloc& lhs_alloc = Alloc(), + const Alloc& rhs_alloc = Alloc(), + AllocatorInvariant check_alloc_invariant = NoOp()) { + { // Test empty/non-empty combinations + { // assign from a non-empty container into an empty one + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + Map copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.at(1) == 1); + assert(copy.at(2) == 3); + assert(copy.at(4) == 4); + assert(copy.at(5) == 2); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.at(1) == 1); + assert(orig.at(2) == 3); + assert(orig.at(4) == 4); + assert(orig.at(5) == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // assign from an empty container into an empty one + using Map = std::unordered_map, std::equal_to, Alloc>; + + const Map orig(rhs_alloc); + Map copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(copy.begin() == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + { // assign from an empty container into a non-empty one + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + const Map orig(rhs_alloc); + Map copy(begin(arr), end(arr), 0, std::hash(), std::equal_to(), lhs_alloc); + copy = orig; + // Depending on whether the allocator is propagated the bucked count can change + LIBCPP_ASSERT(copy.bucket_count() == 5 || copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(copy.begin() == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + } + { // Test empty/one-element copies. In our implementation that's a special case. + { // assign from a single-element container into an empty one + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 1)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + Map copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.at(1) == 1); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.at(1) == 1); + } + { // assign from an empty container into a single-element one + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 1)}; + const Map orig(rhs_alloc); + Map copy(begin(arr), end(arr), 0, std::hash(), std::equal_to(), lhs_alloc); + copy = orig; + // Depending on whether the allocator is propagated the bucked count can change + LIBCPP_ASSERT(copy.bucket_count() == 2 || copy.bucket_count() == 0); + assert(copy.size() == 0); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + } + } + { // Ensure that self-assignment works correctly + { // with a non-empty map + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + orig = static_cast(orig); + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.at(1) == 1); + assert(orig.at(2) == 3); + assert(orig.at(4) == 4); + assert(orig.at(5) == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // with an empty map + using Map = std::unordered_map, std::equal_to, Alloc>; + + Map orig(rhs_alloc); + orig = static_cast(orig); + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + } + { // check assignment into a non-empty map + { // LHS already contains elements, but fewer than the RHS + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V lhs_arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + const Map orig(begin(lhs_arr), end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + V rhs_arr[] = {V(10, 4), V(13, 5)}; + Map copy(begin(rhs_arr), end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.at(1) == 1); + assert(copy.at(2) == 3); + assert(copy.at(4) == 4); + assert(copy.at(5) == 2); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.at(1) == 1); + assert(orig.at(2) == 3); + assert(orig.at(4) == 4); + assert(orig.at(5) == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // LHS contains the same number of elements as the RHS + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V lhs_arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + const Map orig(begin(lhs_arr), end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + V rhs_arr[] = {V(10, 4), V(13, 5), V(12, 324), V(0, 54)}; + Map copy(begin(rhs_arr), end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.at(1) == 1); + assert(copy.at(2) == 3); + assert(copy.at(4) == 4); + assert(copy.at(5) == 2); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.at(1) == 1); + assert(orig.at(2) == 3); + assert(orig.at(4) == 4); + assert(orig.at(5) == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // LHS already contains more elements than the RHS + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V lhs_arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + const Map orig(begin(lhs_arr), end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + V rhs_arr[] = {V(10, 4), V(13, 5), V(12, 324), V(0, 54), V(50, 5), V(2, 5)}; + Map copy(begin(rhs_arr), end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.at(1) == 1); + assert(copy.at(2) == 3); + assert(copy.at(4) == 4); + assert(copy.at(5) == 2); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.at(1) == 1); + assert(orig.at(2) == 3); + assert(orig.at(4) == 4); + assert(orig.at(5) == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); } -#if TEST_STD_VER >= 11 - { - typedef min_allocator > A; - typedef std::unordered_map, test_equal_to, A > C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), +} + +void test() { + test_alloc > >(); + test_alloc > >(); + + { // Make sure we're allocating/deallocating nodes with the correct allocator + // See https://llvm.org/PR29001 (report is for std::map, but the unordered containers have the same optimization) + class AssertEmpty { + std::vector* lhs_allocs_; + std::vector* rhs_allocs_; + + public: + AssertEmpty(std::vector& lhs_allocs, std::vector& rhs_allocs) + : lhs_allocs_(&lhs_allocs), rhs_allocs_(&rhs_allocs) {} + + void operator()() { + assert(lhs_allocs_->empty()); + assert(rhs_allocs_->empty()); + } }; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A()); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A()); - c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.at(1) == "one"); - assert(c.at(2) == "two"); - assert(c.at(3) == "three"); - assert(c.at(4) == "four"); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A()); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + std::vector lhs_allocs; + std::vector rhs_allocs; + test_alloc > >( + lhs_allocs, rhs_allocs, AssertEmpty(lhs_allocs, rhs_allocs)); + } + + { // Ensure that the hasher is copied + { // when the container is non-empty + using V = std::pair; + using Map = std::unordered_map >; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + const Map orig(begin(arr), end(arr), 0, test_hash(5)); + Map copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.hash_function() == test_hash(5)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.hash_function() == test_hash(5)); + } + { // when the container is empty + using Map = std::unordered_map >; + + const Map orig(0, test_hash(5)); + Map copy; + copy = orig; + assert(copy.empty()); + assert(copy.hash_function() == test_hash(5)); + + // Check that orig is still what is expected + assert(orig.empty()); + assert(orig.hash_function() == test_hash(5)); + } } -#endif + + { // Ensure that the equality comparator is copied + { // when the container is non-empty + using V = std::pair; + using Map = std::unordered_map, test_equal_to >; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), test_equal_to(23)); + Map copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.key_eq() == test_equal_to(23)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(copy.key_eq() == test_equal_to(23)); + } + { // when the container is empty + using Map = std::unordered_map, test_equal_to >; + + const Map orig(0, std::hash(), test_equal_to(23)); + Map copy; + copy = orig; + assert(copy.size() == 0); + assert(copy.key_eq() == test_equal_to(23)); + + // Check that orig is still what is expected + assert(orig.size() == 0); + assert(copy.key_eq() == test_equal_to(23)); + } + } + + { // Ensure that the max load factor is copied + { // when the container is non-empty + using V = std::pair; + using Map = std::unordered_map; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + Map orig(begin(arr), end(arr)); + orig.max_load_factor(33.f); + Map copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.max_load_factor() == 33.f); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.max_load_factor() == 33.f); + } + { // when the container is empty + using Map = std::unordered_map; + + Map orig; + orig.max_load_factor(17.f); + Map copy; + copy = orig; + assert(copy.size() == 0); + assert(copy.max_load_factor() == 17.f); + + // Check that orig is still what is expected + assert(orig.size() == 0); + assert(orig.max_load_factor() == 17.f); + } + } + + { // Check that pocca is handled properly + { // pocca == true_type + { // when the container is non-empty + using V = std::pair; + using Alloc = other_allocator; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), Alloc(3)); + Map copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(3)); + } + { // when the container is empty + using Alloc = other_allocator >; + using Map = std::unordered_map, std::equal_to, Alloc>; + + const Map orig(Alloc(3)); + Map copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(3)); + } + } + { // pocca == false_type + { // when the container is non-empty + using V = std::pair; + using Alloc = test_allocator; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), Alloc(3)); + Map copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(1)); + } + { // when the container is empty + using Alloc = test_allocator >; + using Map = std::unordered_map, std::equal_to, Alloc>; + + const Map orig(Alloc(3)); + Map copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(1)); + } + } + } +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/copy.pass.cpp b/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/copy.pass.cpp index 793807babbdca..36083d54228fa 100644 --- a/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/copy.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/copy.pass.cpp @@ -14,136 +14,134 @@ // unordered_map(const unordered_map& u); -#include -#include #include #include #include #include +#include -#include "test_macros.h" #include "../../../test_compare.h" #include "../../../test_hash.h" -#include "test_allocator.h" #include "min_allocator.h" +#include "test_allocator.h" +#include "test_macros.h" -int main(int, char**) { - { - typedef std::unordered_map, - test_equal_to, - test_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - test_allocator >(10)); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.at(1) == "one"); - assert(c.at(2) == "two"); - assert(c.at(3) == "three"); - assert(c.at(4) == "four"); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (test_allocator >(10))); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +void test_alloc() { + { // Simple check + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 2), V(2, 4), V(3, 1)}; + const Map orig(std::begin(arr), std::end(arr)); + Map copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 3); + assert(copy.at(1) == 2); + assert(copy.at(2) == 4); + assert(copy.at(3) == 1); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 3); + assert(orig.at(1) == 2); + assert(orig.at(2) == 4); + assert(orig.at(3) == 1); + } + { // single element copy + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 2)}; + const Map orig(std::begin(arr), std::end(arr)); + Map copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.at(1) == 2); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.at(1) == 2); + } + { // Copy empty map + using Map = std::unordered_map, std::equal_to, Alloc>; + + const Map orig; + Map copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); } -#if TEST_STD_VER >= 11 - { - typedef std::unordered_map, - test_equal_to, - other_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - other_allocator >(10)); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.at(1) == "one"); - assert(c.at(2) == "two"); - assert(c.at(3) == "three"); - assert(c.at(4) == "four"); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (other_allocator >(-2))); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + { // Ensure that the hash function is copied + using Map = std::unordered_map, std::equal_to, Alloc>; + const Map orig(0, test_hash(23)); + Map copy = orig; + assert(copy.hash_function() == test_hash(23)); + + // Check that orig is still what is expected + assert(orig.hash_function() == test_hash(23)); + } + { // Ensure that the quality comparator is copied + using Map = std::unordered_map, test_equal_to, Alloc>; + const Map orig(0, std::hash(), test_equal_to(56)); + Map copy = orig; + assert(copy.key_eq() == test_equal_to(56)); + + // Check that orig is still what is expected + assert(orig.key_eq() == test_equal_to(56)); } - { - typedef std::unordered_map, - test_equal_to, - min_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - min_allocator >()); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.at(1) == "one"); - assert(c.at(2) == "two"); - assert(c.at(3) == "three"); - assert(c.at(4) == "four"); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (min_allocator >())); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +} + +void test() { + test_alloc > >(); + test_alloc > >(); + + { // Ensure that the allocator is copied + using V = std::pair; + using Map = std::unordered_map, std::equal_to, test_allocator >; + + V arr[] = {V(1, 1), V(2, 3), V(3, 6)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), test_allocator(10)); + Map copy = orig; + assert(copy.size() == 3); + assert(copy.get_allocator() == test_allocator(10)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.get_allocator() == test_allocator(10)); + assert(orig.get_allocator().get_id() != test_alloc_base::moved_value); } -#endif + + { // Ensure that soccc is handled properly + using V = std::pair; + using Map = std::unordered_map, std::equal_to, other_allocator >; + + V arr[] = {V(1, 1), V(2, 3), V(3, 6)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), other_allocator(10)); + Map copy = orig; + assert(copy.size() == 3); + assert(copy.get_allocator() == other_allocator(-2)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.get_allocator() == other_allocator(10)); + } +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/copy_alloc.pass.cpp b/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/copy_alloc.pass.cpp index 65c49f4fdcd00..652e8ce10ea45 100644 --- a/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/copy_alloc.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.map/unord.map.cnstr/copy_alloc.pass.cpp @@ -14,127 +14,109 @@ // unordered_map(const unordered_map& u, const allocator_type& a); -#include -#include #include #include #include #include +#include -#include "test_macros.h" #include "../../../test_compare.h" #include "../../../test_hash.h" -#include "test_allocator.h" #include "min_allocator.h" +#include "test_allocator.h" +#include "test_macros.h" -int main(int, char**) { - { - typedef std::unordered_map, - test_equal_to, - test_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - test_allocator >(10)); - C c(c0, test_allocator >(5)); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.at(1) == "one"); - assert(c.at(2) == "two"); - assert(c.at(3) == "three"); - assert(c.at(4) == "four"); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (test_allocator >(5))); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +void test_alloc(const Alloc& new_alloc) { + { // Simple check + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 2), V(2, 4), V(3, 1)}; + const Map orig(std::begin(arr), std::end(arr)); + Map copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 3); + assert(copy.at(1) == 2); + assert(copy.at(2) == 4); + assert(copy.at(3) == 1); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 3); + assert(orig.at(1) == 2); + assert(orig.at(2) == 4); + assert(orig.at(3) == 1); + } + { // single element check + using V = std::pair; + using Map = std::unordered_map, std::equal_to, Alloc>; + + V arr[] = {V(1, 2)}; + const Map orig(std::begin(arr), std::end(arr)); + Map copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.at(1) == 2); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.at(1) == 2); + } + { // Copy empty map + using Map = std::unordered_map, std::equal_to, Alloc>; + + const Map orig; + Map copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); } -#if TEST_STD_VER >= 11 - { - typedef std::unordered_map, - test_equal_to, - min_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - min_allocator >()); - C c(c0, min_allocator >()); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.at(1) == "one"); - assert(c.at(2) == "two"); - assert(c.at(3) == "three"); - assert(c.at(4) == "four"); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (min_allocator >())); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + { // Ensure that the hash function is copied + using Map = std::unordered_map, std::equal_to, Alloc>; + const Map orig(0, test_hash(23)); + Map copy(orig, new_alloc); + assert(copy.hash_function() == test_hash(23)); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + assert(orig.hash_function() == test_hash(23)); } - { - typedef explicit_allocator> A; - typedef std::unordered_map, test_equal_to, A > C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A{}); - C c(c0, A{}); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.at(1) == "one"); - assert(c.at(2) == "two"); - assert(c.at(3) == "three"); - assert(c.at(4) == "four"); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A{}); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + { // Ensure that the quality comparator is copied + using Map = std::unordered_map, test_equal_to, Alloc>; + const Map orig(0, std::hash(), test_equal_to(56)); + Map copy(orig, new_alloc); + assert(copy.key_eq() == test_equal_to(56)); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + assert(orig.key_eq() == test_equal_to(56)); } -#endif +} + +void test() { + test_alloc(std::allocator >()); + test_alloc(min_allocator >()); + test_alloc(test_allocator >(25)); +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/assign_copy.pass.cpp b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/assign_copy.pass.cpp index 8897b6abd1439..938b6beccd141 100644 --- a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/assign_copy.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/assign_copy.pass.cpp @@ -14,153 +14,473 @@ // unordered_multimap& operator=(const unordered_multimap& u); -#include -#include -#include +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + +#include #include #include -#include -#include #include +#include +#include -#include "test_macros.h" #include "../../../check_consecutive.h" #include "../../../test_compare.h" #include "../../../test_hash.h" -#include "test_allocator.h" #include "min_allocator.h" +#include "test_allocator.h" +#include "test_macros.h" -int main(int, char**) { - { - typedef test_allocator > A; - typedef std::unordered_multimap, test_equal_to, A > C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A(10)); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A(4)); - c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - std::multiset s; - s.insert("one"); - s.insert("four"); - CheckConsecutiveKeys(c.find(1), c.end(), 1, s); - s.insert("two"); - s.insert("four"); - CheckConsecutiveKeys(c.find(2), c.end(), 2, s); - s.insert("three"); - CheckConsecutiveKeys(c.find(3), c.end(), 3, s); - s.insert("four"); - CheckConsecutiveKeys(c.find(4), c.end(), 4, s); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A(4)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +class tracking_allocator { + std::vector* allocs_; + + template + friend class tracking_allocator; + +public: + using value_type = T; + using propagate_on_container_copy_assignment = std::true_type; + + tracking_allocator(std::vector& allocs) : allocs_(&allocs) {} + + template + tracking_allocator(const tracking_allocator& other) : allocs_(other.allocs_) {} + + T* allocate(std::size_t n) { + T* allocation = std::allocator().allocate(n); + allocs_->push_back(allocation); + return allocation; } - { - typedef std::unordered_multimap C; - typedef std::pair P; - const P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c(a, a + sizeof(a) / sizeof(a[0])); - C* p = &c; - c = *p; - assert(c.size() == 6); - assert(std::is_permutation(c.begin(), c.end(), a)); + + void deallocate(T* ptr, std::size_t n) TEST_NOEXCEPT { + auto res = std::remove(allocs_->begin(), allocs_->end(), ptr); + assert(res != allocs_->end() && "Trying to deallocate memory from different allocator?"); + allocs_->erase(res); + std::allocator().deallocate(ptr, n); } - { - typedef other_allocator > A; - typedef std::unordered_multimap, test_equal_to, A > C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A(10)); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A(4)); - c = c0; - assert(c.bucket_count() >= 7); - assert(c.size() == 6); - std::multiset s; - s.insert("one"); - s.insert("four"); - CheckConsecutiveKeys(c.find(1), c.end(), 1, s); - s.insert("two"); - s.insert("four"); - CheckConsecutiveKeys(c.find(2), c.end(), 2, s); - s.insert("three"); - CheckConsecutiveKeys(c.find(3), c.end(), 3, s); - s.insert("four"); - CheckConsecutiveKeys(c.find(4), c.end(), 4, s); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A(10)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + friend bool operator==(const tracking_allocator& lhs, const tracking_allocator& rhs) { + return lhs.allocs_ == rhs.allocs_; + } + + friend bool operator!=(const tracking_allocator& lhs, const tracking_allocator& rhs) { + return lhs.allocs_ != rhs.allocs_; + } +}; + +struct NoOp { + void operator()() {} +}; + +template +void test_alloc(const Alloc& lhs_alloc = Alloc(), + const Alloc& rhs_alloc = Alloc(), + AllocatorInvariant check_alloc_invariant = NoOp()) { + { // Test empty/non-empty combinations + { // assign from a non-empty container into an empty one + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 3), V(4, 4), V(4, 2)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + Map copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.find(1)->second == 1); + assert(copy.find(2)->second == 3); + { + auto range = copy.equal_range(4); + auto first_element = std::next(range.first, 0); + auto second_element = std::next(range.first, 1); + auto end = std::next(range.first, 2); + + assert(range.second == end); + + assert(first_element->second == 2 || first_element->second == 4); + assert(second_element->second == 2 || second_element->second == 4); + assert(second_element->second != range.first->second); + } + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.find(1)->second == 1); + assert(orig.find(2)->second == 3); + { + auto range = orig.equal_range(4); + auto first_element = std::next(range.first, 0); + auto second_element = std::next(range.first, 1); + auto end = std::next(range.first, 2); + + assert(range.second == end); + + assert(first_element->second == 2 || first_element->second == 4); + assert(second_element->second == 2 || second_element->second == 4); + assert(second_element->second != range.first->second); + } + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // assign from an empty container into an empty one + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + const Map orig(rhs_alloc); + Map copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(copy.begin() == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + { // assign from an empty container into a non-empty one + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + const Map orig(rhs_alloc); + Map copy(begin(arr), end(arr), 0, std::hash(), std::equal_to(), lhs_alloc); + copy = orig; + // Depending on whether the allocator is propagated the bucked count can change + LIBCPP_ASSERT(copy.bucket_count() == 5 || copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(copy.begin() == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + } + { // Test empty/one-element copies. In our implementation that's a special case. + { // assign from a single-element container into an empty one + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 1)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + Map copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.find(1)->second == 1); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.find(1)->second == 1); + } + { // assign from an empty container into a single-element one + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 1)}; + const Map orig(rhs_alloc); + Map copy(begin(arr), end(arr), 0, std::hash(), std::equal_to(), lhs_alloc); + copy = orig; + // Depending on whether the allocator is propagated the bucked count can change + LIBCPP_ASSERT(copy.bucket_count() == 2 || copy.bucket_count() == 0); + assert(copy.size() == 0); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + } + } + { // Ensure that self-assignment works correctly + { // with a non-empty map + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + orig = static_cast(orig); + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.find(1)->second == 1); + assert(orig.find(2)->second == 3); + assert(orig.find(4)->second == 4); + assert(orig.find(5)->second == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // with an empty map + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + Map orig(rhs_alloc); + orig = static_cast(orig); + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); } -#if TEST_STD_VER >= 11 - { - typedef min_allocator > A; - typedef std::unordered_multimap, test_equal_to, A > C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), + { // check assignment into a non-empty map + { // LHS already contains elements, but fewer than the RHS + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V lhs_arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + const Map orig(begin(lhs_arr), end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + V rhs_arr[] = {V(10, 4), V(13, 5)}; + Map copy(begin(rhs_arr), end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.find(1)->second == 1); + assert(copy.find(2)->second == 3); + assert(copy.find(4)->second == 4); + assert(copy.find(5)->second == 2); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.find(1)->second == 1); + assert(orig.find(2)->second == 3); + assert(orig.find(4)->second == 4); + assert(orig.find(5)->second == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // LHS contains the same number of elements as the RHS + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V lhs_arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + const Map orig(begin(lhs_arr), end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + V rhs_arr[] = {V(10, 4), V(13, 5), V(12, 324), V(0, 54)}; + Map copy(begin(rhs_arr), end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.find(1)->second == 1); + assert(copy.find(2)->second == 3); + assert(copy.find(4)->second == 4); + assert(copy.find(5)->second == 2); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.find(1)->second == 1); + assert(orig.find(2)->second == 3); + assert(orig.find(4)->second == 4); + assert(orig.find(5)->second == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // LHS already contains more elements than the RHS + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V lhs_arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)}; + const Map orig(begin(lhs_arr), end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + V rhs_arr[] = {V(10, 4), V(13, 5), V(12, 324), V(0, 54), V(50, 5), V(2, 5)}; + Map copy(begin(rhs_arr), end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.find(1)->second == 1); + assert(copy.find(2)->second == 3); + assert(copy.find(4)->second == 4); + assert(copy.find(5)->second == 2); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.find(1)->second == 1); + assert(orig.find(2)->second == 3); + assert(orig.find(4)->second == 4); + assert(orig.find(5)->second == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + } +} + +void test() { + test_alloc > >(); + test_alloc > >(); + + { // Make sure we're allocating/deallocating nodes with the correct allocator + // See https://llvm.org/PR29001 (report is for std::map, but the unordered containers have the same optimization) + class AssertEmpty { + std::vector* lhs_allocs_; + std::vector* rhs_allocs_; + + public: + AssertEmpty(std::vector& lhs_allocs, std::vector& rhs_allocs) + : lhs_allocs_(&lhs_allocs), rhs_allocs_(&rhs_allocs) {} + + void operator()() { + assert(lhs_allocs_->empty()); + assert(rhs_allocs_->empty()); + } }; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A()); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A()); - c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - std::multiset s; - s.insert("one"); - s.insert("four"); - CheckConsecutiveKeys(c.find(1), c.end(), 1, s); - s.insert("two"); - s.insert("four"); - CheckConsecutiveKeys(c.find(2), c.end(), 2, s); - s.insert("three"); - CheckConsecutiveKeys(c.find(3), c.end(), 3, s); - s.insert("four"); - CheckConsecutiveKeys(c.find(4), c.end(), 4, s); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A()); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + std::vector lhs_allocs; + std::vector rhs_allocs; + test_alloc > >( + lhs_allocs, rhs_allocs, AssertEmpty(lhs_allocs, rhs_allocs)); + } + + { // Ensure that the hasher is copied + { // when the container is non-empty + using V = std::pair; + using Map = std::unordered_multimap >; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + const Map orig(begin(arr), end(arr), 0, test_hash(5)); + Map copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.hash_function() == test_hash(5)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.hash_function() == test_hash(5)); + } + { // when the container is empty + using Map = std::unordered_multimap >; + + const Map orig(0, test_hash(5)); + Map copy; + copy = orig; + assert(copy.empty()); + assert(copy.hash_function() == test_hash(5)); + + // Check that orig is still what is expected + assert(orig.empty()); + assert(orig.hash_function() == test_hash(5)); + } } -#endif + + { // Ensure that the equality comparator is copied + { // when the container is non-empty + using V = std::pair; + using Map = std::unordered_multimap, test_equal_to >; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), test_equal_to(23)); + Map copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.key_eq() == test_equal_to(23)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(copy.key_eq() == test_equal_to(23)); + } + { // when the container is empty + using Map = std::unordered_multimap, test_equal_to >; + + const Map orig(0, std::hash(), test_equal_to(23)); + Map copy; + copy = orig; + assert(copy.size() == 0); + assert(copy.key_eq() == test_equal_to(23)); + + // Check that orig is still what is expected + assert(orig.size() == 0); + assert(copy.key_eq() == test_equal_to(23)); + } + } + + { // Ensure that the max load factor is copied + { // when the container is non-empty + using V = std::pair; + using Map = std::unordered_multimap; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + Map orig(begin(arr), end(arr)); + orig.max_load_factor(33.f); + Map copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.max_load_factor() == 33.f); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.max_load_factor() == 33.f); + } + { // when the container is empty + using Map = std::unordered_multimap; + + Map orig; + orig.max_load_factor(17.f); + Map copy; + copy = orig; + assert(copy.size() == 0); + assert(copy.max_load_factor() == 17.f); + + // Check that orig is still what is expected + assert(orig.size() == 0); + assert(orig.max_load_factor() == 17.f); + } + } + + { // Check that pocca is handled properly + { // pocca == true_type + { // when the container is non-empty + using V = std::pair; + using Alloc = other_allocator; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), Alloc(3)); + Map copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(3)); + } + { // when the container is empty + using Alloc = other_allocator >; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + const Map orig(Alloc(3)); + Map copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(3)); + } + } + { // pocca == false_type + { // when the container is non-empty + using V = std::pair; + using Alloc = test_allocator; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 1), V(2, 2), V(3, 3)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), Alloc(3)); + Map copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(1)); + } + { // when the container is empty + using Alloc = test_allocator >; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + const Map orig(Alloc(3)); + Map copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(1)); + } + } + } +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/copy.pass.cpp b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/copy.pass.cpp index e369ce2ac268e..36e6a0487a073 100644 --- a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/copy.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/copy.pass.cpp @@ -29,144 +29,144 @@ #include "test_allocator.h" #include "min_allocator.h" -int main(int, char**) { - { - typedef std::unordered_multimap, - test_equal_to, - test_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - test_allocator >(10)); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - std::multiset s; - s.insert("one"); - s.insert("four"); - CheckConsecutiveKeys(c.find(1), c.end(), 1, s); - s.insert("two"); - s.insert("four"); - CheckConsecutiveKeys(c.find(2), c.end(), 2, s); - s.insert("three"); - CheckConsecutiveKeys(c.find(3), c.end(), 3, s); - s.insert("four"); - CheckConsecutiveKeys(c.find(4), c.end(), 4, s); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (test_allocator >(10))); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +void test_alloc() { + { // Simple check + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 2), V(2, 4), V(3, 1), V(3, 2)}; + const Map orig(std::begin(arr), std::end(arr)); + Map copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.find(1)->second == 2); + assert(copy.find(2)->second == 4); + { + auto range = copy.equal_range(3); + auto first_element = std::next(range.first, 0); + auto second_element = std::next(range.first, 1); + auto end = std::next(range.first, 2); + + assert(range.second == end); + + assert(first_element->second == 1 || first_element->second == 2); + assert(second_element->second == 1 || second_element->second == 2); + assert(second_element->second != range.first->second); + } + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.find(1)->second == 2); + assert(orig.find(2)->second == 4); + { + auto range = orig.equal_range(3); + auto first_element = std::next(range.first, 0); + auto second_element = std::next(range.first, 1); + auto end = std::next(range.first, 2); + + assert(range.second == end); + + assert(first_element->second == 1 || first_element->second == 2); + assert(second_element->second == 1 || second_element->second == 2); + assert(second_element->second != range.first->second); + } + } + { // single element copy + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 2)}; + const Map orig(std::begin(arr), std::end(arr)); + Map copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.find(1)->second == 2); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.find(1)->second == 2); + } + { // Copy empty map + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + const Map orig; + Map copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + } + { // Ensure that the hash function is copied + using Map = std::unordered_multimap, std::equal_to, Alloc>; + const Map orig(0, test_hash(23)); + Map copy = orig; + assert(copy.hash_function() == test_hash(23)); + + // Check that orig is still what is expected + assert(orig.hash_function() == test_hash(23)); + } + { // Ensure that the quality comparator is copied + using Map = std::unordered_multimap, test_equal_to, Alloc>; + const Map orig(0, std::hash(), test_equal_to(56)); + Map copy = orig; + assert(copy.key_eq() == test_equal_to(56)); + + // Check that orig is still what is expected + assert(orig.key_eq() == test_equal_to(56)); } -#if TEST_STD_VER >= 11 - { - typedef std::unordered_multimap, - test_equal_to, - other_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - other_allocator >(10)); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - std::multiset s; - s.insert("one"); - s.insert("four"); - CheckConsecutiveKeys(c.find(1), c.end(), 1, s); - s.insert("two"); - s.insert("four"); - CheckConsecutiveKeys(c.find(2), c.end(), 2, s); - s.insert("three"); - CheckConsecutiveKeys(c.find(3), c.end(), 3, s); - s.insert("four"); - CheckConsecutiveKeys(c.find(4), c.end(), 4, s); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (other_allocator >(-2))); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +} + +void test() { + test_alloc > >(); + test_alloc > >(); + + { // Ensure that the allocator is copied + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, test_allocator >; + + V arr[] = {V(1, 1), V(2, 3), V(3, 6)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), test_allocator(10)); + Map copy = orig; + assert(copy.size() == 3); + assert(copy.get_allocator() == test_allocator(10)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.get_allocator() == test_allocator(10)); + assert(orig.get_allocator().get_id() != test_alloc_base::moved_value); } - { - typedef std::unordered_multimap, - test_equal_to, - min_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - min_allocator >()); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - std::multiset s; - s.insert("one"); - s.insert("four"); - CheckConsecutiveKeys(c.find(1), c.end(), 1, s); - s.insert("two"); - s.insert("four"); - CheckConsecutiveKeys(c.find(2), c.end(), 2, s); - s.insert("three"); - CheckConsecutiveKeys(c.find(3), c.end(), 3, s); - s.insert("four"); - CheckConsecutiveKeys(c.find(4), c.end(), 4, s); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (min_allocator >())); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + { // Ensure that soccc is handled properly + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, other_allocator >; + + V arr[] = {V(1, 1), V(2, 3), V(3, 6)}; + const Map orig(begin(arr), end(arr), 0, std::hash(), std::equal_to(), other_allocator(10)); + Map copy = orig; + assert(copy.size() == 3); + assert(copy.get_allocator() == other_allocator(-2)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.get_allocator() == other_allocator(10)); } -#endif +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/copy_alloc.pass.cpp b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/copy_alloc.pass.cpp index 25348786a3607..e9e3668b8b2be 100644 --- a/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/copy_alloc.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.multimap/unord.multimap.cnstr/copy_alloc.pass.cpp @@ -29,135 +29,120 @@ #include "test_allocator.h" #include "min_allocator.h" -int main(int, char**) { - { - typedef std::unordered_multimap, - test_equal_to, - test_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - test_allocator >(10)); - C c(c0, test_allocator >(5)); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - std::multiset s; - s.insert("one"); - s.insert("four"); - CheckConsecutiveKeys(c.find(1), c.end(), 1, s); - s.insert("two"); - s.insert("four"); - CheckConsecutiveKeys(c.find(2), c.end(), 2, s); - s.insert("three"); - CheckConsecutiveKeys(c.find(3), c.end(), 3, s); - s.insert("four"); - CheckConsecutiveKeys(c.find(4), c.end(), 4, s); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (test_allocator >(5))); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +void test_alloc(const Alloc& new_alloc) { + { // Simple check + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 2), V(2, 4), V(3, 1), V(3, 2)}; + const Map orig(std::begin(arr), std::end(arr)); + Map copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.find(1)->second == 2); + assert(copy.find(2)->second == 4); + { + auto range = copy.equal_range(3); + auto first_element = std::next(range.first, 0); + auto second_element = std::next(range.first, 1); + auto end = std::next(range.first, 2); + + assert(range.second == end); + + assert(first_element->second == 1 || first_element->second == 2); + assert(second_element->second == 1 || second_element->second == 2); + assert(second_element->second != range.first->second); + } + + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.find(1)->second == 2); + assert(orig.find(2)->second == 4); + { + auto range = orig.equal_range(3); + auto first_element = std::next(range.first, 0); + auto second_element = std::next(range.first, 1); + auto end = std::next(range.first, 2); + + assert(range.second == end); + + assert(first_element->second == 1 || first_element->second == 2); + assert(second_element->second == 1 || second_element->second == 2); + assert(second_element->second != range.first->second); + } } -#if TEST_STD_VER >= 11 - { - typedef std::unordered_multimap, - test_equal_to, - min_allocator > > - C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, - a + sizeof(a) / sizeof(a[0]), - 7, - test_hash(8), - test_equal_to(9), - min_allocator >()); - C c(c0, min_allocator >()); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - std::multiset s; - s.insert("one"); - s.insert("four"); - CheckConsecutiveKeys(c.find(1), c.end(), 1, s); - s.insert("two"); - s.insert("four"); - CheckConsecutiveKeys(c.find(2), c.end(), 2, s); - s.insert("three"); - CheckConsecutiveKeys(c.find(3), c.end(), 3, s); - s.insert("four"); - CheckConsecutiveKeys(c.find(4), c.end(), 4, s); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == (min_allocator >())); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + { // single element check + using V = std::pair; + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + V arr[] = {V(1, 2)}; + const Map orig(std::begin(arr), std::end(arr)); + Map copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.find(1)->second == 2); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.find(1)->second == 2); } - { - typedef explicit_allocator> A; - typedef std::unordered_multimap, test_equal_to, A > C; - typedef std::pair P; - P a[] = { - P(1, "one"), - P(2, "two"), - P(3, "three"), - P(4, "four"), - P(1, "four"), - P(2, "four"), - }; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A{}); - C c(c0, A{}); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - std::multiset s; - s.insert("one"); - s.insert("four"); - CheckConsecutiveKeys(c.find(1), c.end(), 1, s); - s.insert("two"); - s.insert("four"); - CheckConsecutiveKeys(c.find(2), c.end(), 2, s); - s.insert("three"); - CheckConsecutiveKeys(c.find(3), c.end(), 3, s); - s.insert("four"); - CheckConsecutiveKeys(c.find(4), c.end(), 4, s); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A{}); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + { // Copy empty map + using Map = std::unordered_multimap, std::equal_to, Alloc>; + + const Map orig; + Map copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + } + { // Ensure that the hash function is copied + using Map = std::unordered_multimap, std::equal_to, Alloc>; + const Map orig(0, test_hash(23)); + Map copy(orig, new_alloc); + assert(copy.hash_function() == test_hash(23)); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + assert(orig.hash_function() == test_hash(23)); + } + { // Ensure that the quality comparator is copied + using Map = std::unordered_multimap, test_equal_to, Alloc>; + const Map orig(0, std::hash(), test_equal_to(56)); + Map copy(orig, new_alloc); + assert(copy.key_eq() == test_equal_to(56)); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + assert(orig.key_eq() == test_equal_to(56)); } -#endif +} + +void test() { + test_alloc(std::allocator >()); + test_alloc(min_allocator >()); + test_alloc(test_allocator >(25)); +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/assign_copy.pass.cpp b/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/assign_copy.pass.cpp index 248d446b92eb7..e415253c5f60f 100644 --- a/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/assign_copy.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/assign_copy.pass.cpp @@ -14,103 +14,437 @@ // unordered_multiset& operator=(const unordered_multiset& u); -#include +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + #include #include #include #include #include +#include +#include -#include "test_macros.h" -#include "../../../check_consecutive.h" #include "../../../test_compare.h" #include "../../../test_hash.h" -#include "test_allocator.h" #include "min_allocator.h" +#include "test_allocator.h" +#include "test_macros.h" -int main(int, char**) { - { - typedef test_allocator A; - typedef std::unordered_multiset, test_equal_to, A > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A(10)); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A(4)); - c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - CheckConsecutiveValues(c.find(1), c.end(), 1, 2); - CheckConsecutiveValues(c.find(2), c.end(), 2, 2); - CheckConsecutiveValues(c.find(3), c.end(), 3, 1); - CheckConsecutiveValues(c.find(4), c.end(), 4, 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A(4)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +class tracking_allocator { + std::vector* allocs_; + + template + friend class tracking_allocator; + +public: + using value_type = T; + using propagate_on_container_copy_assignment = std::true_type; + + tracking_allocator(std::vector& allocs) : allocs_(&allocs) {} + + template + tracking_allocator(const tracking_allocator& other) : allocs_(other.allocs_) {} + + T* allocate(std::size_t n) { + T* allocation = std::allocator().allocate(n); + allocs_->push_back(allocation); + return allocation; } - { - typedef std::unordered_multiset C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c(a, a + sizeof(a) / sizeof(a[0])); - C* p = &c; - c = *p; - - assert(c.size() == 6); - assert(std::is_permutation(c.begin(), c.end(), a)); + + void deallocate(T* ptr, std::size_t n) TEST_NOEXCEPT { + auto res = std::remove(allocs_->begin(), allocs_->end(), ptr); + assert(res != allocs_->end() && "Trying to deallocate memory from different allocator?"); + allocs_->erase(res); + std::allocator().deallocate(ptr, n); } - { - typedef other_allocator A; - typedef std::unordered_multiset, test_equal_to, A > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A(10)); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A(4)); - c = c0; - assert(c.bucket_count() >= 7); - assert(c.size() == 6); - CheckConsecutiveValues(c.find(1), c.end(), 1, 2); - CheckConsecutiveValues(c.find(2), c.end(), 2, 2); - CheckConsecutiveValues(c.find(3), c.end(), 3, 1); - CheckConsecutiveValues(c.find(4), c.end(), 4, 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A(10)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + friend bool operator==(const tracking_allocator& lhs, const tracking_allocator& rhs) { + return lhs.allocs_ == rhs.allocs_; } -#if TEST_STD_VER >= 11 - { - typedef min_allocator A; - typedef std::unordered_multiset, test_equal_to, A > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A()); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A()); - c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - CheckConsecutiveValues(c.find(1), c.end(), 1, 2); - CheckConsecutiveValues(c.find(2), c.end(), 2, 2); - CheckConsecutiveValues(c.find(3), c.end(), 3, 1); - CheckConsecutiveValues(c.find(4), c.end(), 4, 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A()); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + friend bool operator!=(const tracking_allocator& lhs, const tracking_allocator& rhs) { + return lhs.allocs_ != rhs.allocs_; } -#endif +}; + +struct NoOp { + void operator()() {} +}; + +template +void test_alloc(const Alloc& lhs_alloc = Alloc(), + const Alloc& rhs_alloc = Alloc(), + AllocatorInvariant check_alloc_invariant = NoOp()) { + { // Test empty/non-empty combinations + { // assign from a non-empty container into an empty one + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1, 2, 4, 4}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + Set copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(4) == 2); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 2); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // assign from an empty container into an empty one + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + const Set orig(rhs_alloc); + Set copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(copy.begin() == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + { // assign from an empty container into a non-empty one + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1, 2, 4, 4}; + const Set orig(rhs_alloc); + Set copy(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), lhs_alloc); + copy = orig; + // Depending on whether the allocator is propagated the bucked count can change + LIBCPP_ASSERT(copy.bucket_count() == 5 || copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(copy.begin() == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + } + { // Test empty/one-element copies. In our implementation that's a special case. + { // assign from a single-element container into an empty one + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + Set copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.count(1) == 1); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.count(1) == 1); + } + { // assign from an empty container into a single-element one + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1}; + const Set orig(rhs_alloc); + Set copy(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), lhs_alloc); + copy = orig; + // Depending on whether the allocator is propagated the bucked count can change + LIBCPP_ASSERT(copy.bucket_count() == 2 || copy.bucket_count() == 0); + assert(copy.size() == 0); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + } + } + { // Ensure that self-assignment works correctly + { // with a non-empty map + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1, 2, 4, 5}; + Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + orig = static_cast(orig); + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 1); + assert(orig.count(5) == 1); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // with an empty map + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + Set orig(rhs_alloc); + orig = static_cast(orig); + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + } + { // check assignment into a non-empty map + { // LHS already contains elements, but fewer than the RHS + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int lhs_arr[] = {1, 2, 4, 5}; + const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + int rhs_arr[] = {10, 13}; + Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(4) == 1); + assert(copy.count(5) == 1); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 1); + assert(orig.count(5) == 1); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // LHS contains the same number of elements as the RHS + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int lhs_arr[] = {1, 2, 4, 5}; + const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + int rhs_arr[] = {10, 13, 12, 0}; + Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(4) == 1); + assert(copy.count(5) == 1); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 1); + assert(orig.count(5) == 1); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // LHS already contains more elements than the RHS + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int lhs_arr[] = {1, 2, 4, 5}; + const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + int rhs_arr[] = {10, 13, 12, 0, 50, 2}; + Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(4) == 1); + assert(copy.count(5) == 1); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 1); + assert(orig.count(5) == 1); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + } +} + +void test() { + test_alloc >(); + test_alloc >(); + + { // Make sure we're allocating/deallocating nodes with the correct allocator + // See https://llvm.org/PR29001 (report is for std::map, but the unordered containers have the same optimization) + class AssertEmpty { + std::vector* lhs_allocs_; + std::vector* rhs_allocs_; + + public: + AssertEmpty(std::vector& lhs_allocs, std::vector& rhs_allocs) + : lhs_allocs_(&lhs_allocs), rhs_allocs_(&rhs_allocs) {} + + void operator()() { + assert(lhs_allocs_->empty()); + assert(rhs_allocs_->empty()); + } + }; + + std::vector lhs_allocs; + std::vector rhs_allocs; + test_alloc >(lhs_allocs, rhs_allocs, AssertEmpty(lhs_allocs, rhs_allocs)); + } + + { // Ensure that the hasher is copied + { // when the container is non-empty + using Set = std::unordered_multiset >; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, test_hash(5)); + Set copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.hash_function() == test_hash(5)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.hash_function() == test_hash(5)); + } + { // when the container is empty + using Set = std::unordered_multiset >; + + const Set orig(0, test_hash(5)); + Set copy; + copy = orig; + assert(copy.empty()); + assert(copy.hash_function() == test_hash(5)); + + // Check that orig is still what is expected + assert(orig.empty()); + assert(orig.hash_function() == test_hash(5)); + } + } + + { // Ensure that the equality comparator is copied + { // when the container is non-empty + using Set = std::unordered_multiset, test_equal_to >; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), test_equal_to(23)); + Set copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.key_eq() == test_equal_to(23)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(copy.key_eq() == test_equal_to(23)); + } + { // when the container is empty + using Set = std::unordered_multiset, test_equal_to >; + + const Set orig(0, std::hash(), test_equal_to(23)); + Set copy; + copy = orig; + assert(copy.size() == 0); + assert(copy.key_eq() == test_equal_to(23)); + + // Check that orig is still what is expected + assert(orig.size() == 0); + assert(copy.key_eq() == test_equal_to(23)); + } + } + + { // Ensure that the max load factor is copied + { // when the container is non-empty + using Set = std::unordered_multiset; + + int arr[] = {1, 2, 3}; + Set orig(std::begin(arr), std::end(arr)); + orig.max_load_factor(33.f); + Set copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.max_load_factor() == 33.f); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.max_load_factor() == 33.f); + } + { // when the container is empty + using Set = std::unordered_multiset; + + Set orig; + orig.max_load_factor(17.f); + Set copy; + copy = orig; + assert(copy.size() == 0); + assert(copy.max_load_factor() == 17.f); + + // Check that orig is still what is expected + assert(orig.size() == 0); + assert(orig.max_load_factor() == 17.f); + } + } + + { // Check that pocca is handled properly + { // pocca == true_type + { // when the container is non-empty + using Alloc = other_allocator; + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), Alloc(3)); + Set copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(3)); + } + { // when the container is empty + using Alloc = other_allocator; + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + const Set orig(Alloc(3)); + Set copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(3)); + } + } + { // pocca == false_type + { // when the container is non-empty + using Alloc = test_allocator; + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), Alloc(3)); + Set copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(1)); + } + { // when the container is empty + using Alloc = test_allocator; + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + const Set orig(Alloc(3)); + Set copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(1)); + } + } + } +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/copy.pass.cpp b/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/copy.pass.cpp index fc577f323e43b..bc74d4a2bbc57 100644 --- a/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/copy.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/copy.pass.cpp @@ -27,72 +27,118 @@ #include "test_allocator.h" #include "min_allocator.h" -int main(int, char**) { - { - typedef std::unordered_multiset, test_equal_to, test_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), test_allocator(10)); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - CheckConsecutiveValues(c.find(1), c.end(), 1, 2); - CheckConsecutiveValues(c.find(2), c.end(), 2, 2); - CheckConsecutiveValues(c.find(3), c.end(), 3, 1); - CheckConsecutiveValues(c.find(4), c.end(), 4, 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == test_allocator(10)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +void test_alloc() { + { // Simple check + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1, 2, 3, 3}; + const Set orig(std::begin(arr), std::end(arr)); + Set copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(3) == 2); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(3) == 2); + } + { // single element copy + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1}; + const Set orig(std::begin(arr), std::end(arr)); + Set copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.count(1) == 1); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.count(1) == 1); + } + { // Copy empty map + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + const Set orig; + Set copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + } + { // Ensure that the hash function is copied + using Set = std::unordered_multiset, std::equal_to, Alloc>; + const Set orig(0, test_hash(23)); + Set copy = orig; + assert(copy.hash_function() == test_hash(23)); + + // Check that orig is still what is expected + assert(orig.hash_function() == test_hash(23)); + } + { // Ensure that the quality comparator is copied + using Set = std::unordered_multiset, test_equal_to, Alloc>; + const Set orig(0, std::hash(), test_equal_to(56)); + Set copy = orig; + assert(copy.key_eq() == test_equal_to(56)); + + // Check that orig is still what is expected + assert(orig.key_eq() == test_equal_to(56)); } -#if TEST_STD_VER >= 11 - { - typedef std::unordered_multiset, test_equal_to, other_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), other_allocator(10)); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - CheckConsecutiveValues(c.find(1), c.end(), 1, 2); - CheckConsecutiveValues(c.find(2), c.end(), 2, 2); - CheckConsecutiveValues(c.find(3), c.end(), 3, 1); - CheckConsecutiveValues(c.find(4), c.end(), 4, 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == other_allocator(-2)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +} + +void test() { + test_alloc >(); + test_alloc >(); + + { // Ensure that the allocator is copied + using Set = std::unordered_multiset, std::equal_to, test_allocator >; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), test_allocator(10)); + Set copy = orig; + assert(copy.size() == 3); + assert(copy.get_allocator() == test_allocator(10)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.get_allocator() == test_allocator(10)); + assert(orig.get_allocator().get_id() != test_alloc_base::moved_value); } - { - typedef std::unordered_multiset, test_equal_to, min_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), min_allocator()); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - CheckConsecutiveValues(c.find(1), c.end(), 1, 2); - CheckConsecutiveValues(c.find(2), c.end(), 2, 2); - CheckConsecutiveValues(c.find(3), c.end(), 3, 1); - CheckConsecutiveValues(c.find(4), c.end(), 4, 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == min_allocator()); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + { // Ensure that soccc is handled properly + using Set = std::unordered_multiset, std::equal_to, other_allocator >; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), other_allocator(10)); + Set copy = orig; + assert(copy.size() == 3); + assert(copy.get_allocator() == other_allocator(-2)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.get_allocator() == other_allocator(10)); } -#endif +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/copy_alloc.pass.cpp b/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/copy_alloc.pass.cpp index 7f45a3f2e4bd4..944865183cdda 100644 --- a/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/copy_alloc.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.multiset/unord.multiset.cnstr/copy_alloc.pass.cpp @@ -14,64 +14,107 @@ // unordered_multiset(const unordered_multiset& u, const allocator_type& a); -#include #include #include #include #include +#include -#include "test_macros.h" -#include "../../../check_consecutive.h" #include "../../../test_compare.h" #include "../../../test_hash.h" -#include "test_allocator.h" #include "min_allocator.h" +#include "test_allocator.h" +#include "test_macros.h" -int main(int, char**) { - { - typedef std::unordered_multiset, test_equal_to, test_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), test_allocator(10)); - C c(c0, test_allocator(5)); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - CheckConsecutiveValues(c.find(1), c.end(), 1, 2); - CheckConsecutiveValues(c.find(2), c.end(), 2, 2); - CheckConsecutiveValues(c.find(3), c.end(), 3, 1); - CheckConsecutiveValues(c.find(4), c.end(), 4, 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == test_allocator(5)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +void test_alloc(const Alloc& new_alloc) { + { // Simple check + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1, 2, 3, 3}; + const Set orig(std::begin(arr), std::end(arr)); + Set copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(3) == 2); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(3) == 2); } -#if TEST_STD_VER >= 11 - { - typedef std::unordered_multiset, test_equal_to, min_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), min_allocator()); - C c(c0, min_allocator()); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 6); - CheckConsecutiveValues(c.find(1), c.end(), 1, 2); - CheckConsecutiveValues(c.find(2), c.end(), 2, 2); - CheckConsecutiveValues(c.find(3), c.end(), 3, 1); - CheckConsecutiveValues(c.find(4), c.end(), 4, 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == min_allocator()); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + { // single element check + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + int arr[] = {1}; + const Set orig(std::begin(arr), std::end(arr)); + Set copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.count(1) == 1); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.count(1) == 1); } -#endif + { // Copy empty map + using Set = std::unordered_multiset, std::equal_to, Alloc>; + + const Set orig; + Set copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + } + { // Ensure that the hash function is copied + using Set = std::unordered_multiset, std::equal_to, Alloc>; + const Set orig(0, test_hash(23)); + Set copy(orig, new_alloc); + assert(copy.hash_function() == test_hash(23)); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + assert(orig.hash_function() == test_hash(23)); + } + { // Ensure that the quality comparator is copied + using Set = std::unordered_multiset, test_equal_to, Alloc>; + const Set orig(0, std::hash(), test_equal_to(56)); + Set copy(orig, new_alloc); + assert(copy.key_eq() == test_equal_to(56)); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + assert(orig.key_eq() == test_equal_to(56)); + } +} + +void test() { + test_alloc(std::allocator()); + test_alloc(min_allocator()); + test_alloc(test_allocator(25)); +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/assign_copy.pass.cpp b/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/assign_copy.pass.cpp index e606e538efcbe..9828b8b459c89 100644 --- a/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/assign_copy.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/assign_copy.pass.cpp @@ -14,115 +14,440 @@ // unordered_set& operator=(const unordered_set& u); -#include +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + #include #include #include #include #include +#include #include +#include -#include "test_macros.h" #include "../../../test_compare.h" #include "../../../test_hash.h" -#include "test_allocator.h" #include "min_allocator.h" +#include "test_allocator.h" +#include "test_macros.h" -int main(int, char**) { - { - typedef test_allocator A; - typedef std::unordered_set, test_equal_to, A > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A(10)); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A(4)); - c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.count(1) == 1); - assert(c.count(2) == 1); - assert(c.count(3) == 1); - assert(c.count(4) == 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A(4)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +class tracking_allocator { + std::vector* allocs_; + + template + friend class tracking_allocator; + +public: + using value_type = T; + using propagate_on_container_copy_assignment = std::true_type; + + tracking_allocator(std::vector& allocs) : allocs_(&allocs) {} + + template + tracking_allocator(const tracking_allocator& other) : allocs_(other.allocs_) {} + + T* allocate(std::size_t n) { + T* allocation = std::allocator().allocate(n); + allocs_->push_back(allocation); + return allocation; } - { - typedef std::unordered_set C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c(a, a + sizeof(a) / sizeof(a[0])); - C* p = &c; - c = *p; - assert(c.size() == 4); - assert(std::is_permutation(c.begin(), c.end(), a)); + + void deallocate(T* ptr, std::size_t n) TEST_NOEXCEPT { + auto res = std::remove(allocs_->begin(), allocs_->end(), ptr); + assert(res != allocs_->end() && "Trying to deallocate memory from different allocator?"); + allocs_->erase(res); + std::allocator().deallocate(ptr, n); } - { - typedef other_allocator A; - typedef std::unordered_set, test_equal_to, A > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A(10)); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A(4)); - c = c0; - assert(c.bucket_count() >= 5); - assert(c.size() == 4); - assert(c.count(1) == 1); - assert(c.count(2) == 1); - assert(c.count(3) == 1); - assert(c.count(4) == 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A(10)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + friend bool operator==(const tracking_allocator& lhs, const tracking_allocator& rhs) { + return lhs.allocs_ == rhs.allocs_; } -#if TEST_STD_VER >= 11 - { - typedef min_allocator A; - typedef std::unordered_set, test_equal_to, A > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), A()); - C c(a, a + 2, 7, test_hash(2), test_equal_to(3), A()); - c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.count(1) == 1); - assert(c.count(2) == 1); - assert(c.count(3) == 1); - assert(c.count(4) == 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == A()); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + + friend bool operator!=(const tracking_allocator& lhs, const tracking_allocator& rhs) { + return lhs.allocs_ != rhs.allocs_; + } +}; + +struct NoOp { + void operator()() {} +}; + +template +void test_alloc(const Alloc& lhs_alloc = Alloc(), + const Alloc& rhs_alloc = Alloc(), + AllocatorInvariant check_alloc_invariant = NoOp()) { + { // Test empty/non-empty combinations + { // assign from a non-empty container into an empty one + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1, 2, 4, 5}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + Set copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(4) == 1); + assert(copy.count(5) == 1); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 1); + assert(orig.count(5) == 1); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // assign from an empty container into an empty one + using Set = std::unordered_set, std::equal_to, Alloc>; + + const Set orig(rhs_alloc); + Set copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(copy.begin() == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + { // assign from an empty container into a non-empty one + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1, 2, 4, 5}; + const Set orig(rhs_alloc); + Set copy(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), lhs_alloc); + copy = orig; + // Depending on whether the allocator is propagated the bucked count can change + LIBCPP_ASSERT(copy.bucket_count() == 5 || copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(copy.begin() == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + } + { // Test empty/one-element copies. In our implementation that's a special case. + { // assign from a single-element container into an empty one + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + Set copy(lhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.count(1) == 1); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.count(1) == 1); + } + { // assign from an empty container into a single-element one + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1}; + const Set orig(rhs_alloc); + Set copy(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), lhs_alloc); + copy = orig; + // Depending on whether the allocator is propagated the bucked count can change + LIBCPP_ASSERT(copy.bucket_count() == 2 || copy.bucket_count() == 0); + assert(copy.size() == 0); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + } + } + { // Ensure that self-assignment works correctly + { // with a non-empty map + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1, 2, 4, 5}; + Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), rhs_alloc); + orig = static_cast(orig); + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 1); + assert(orig.count(5) == 1); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // with an empty map + using Set = std::unordered_set, std::equal_to, Alloc>; + + Set orig(rhs_alloc); + orig = static_cast(orig); + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + assert(orig.begin() == orig.end()); + } + check_alloc_invariant(); + } + { // check assignment into a non-empty map + { // LHS already contains elements, but fewer than the RHS + using Set = std::unordered_set, std::equal_to, Alloc>; + + int lhs_arr[] = {1, 2, 4, 5}; + const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + int rhs_arr[] = {10, 13}; + Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(4) == 1); + assert(copy.count(5) == 1); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 1); + assert(orig.count(5) == 1); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // LHS contains the same number of elements as the RHS + using Set = std::unordered_set, std::equal_to, Alloc>; + + int lhs_arr[] = {1, 2, 4, 5}; + const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + int rhs_arr[] = {10, 13, 12, 0}; + Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(4) == 1); + assert(copy.count(5) == 1); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 1); + assert(orig.count(5) == 1); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); + { // LHS already contains more elements than the RHS + using Set = std::unordered_set, std::equal_to, Alloc>; + + int lhs_arr[] = {1, 2, 4, 5}; + const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + + int rhs_arr[] = {10, 13, 12, 0, 50, 2}; + Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash(), std::equal_to(), rhs_alloc); + copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 4); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(4) == 1); + assert(copy.count(5) == 1); + assert(std::next(copy.begin(), 4) == copy.end()); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 4); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(4) == 1); + assert(orig.count(5) == 1); + assert(std::next(orig.begin(), 4) == orig.end()); + } + check_alloc_invariant(); } -#endif - { // Test with std::pair, since we have some special handling for pairs inside __hash_table - struct pair_hash { - size_t operator()(std::pair val) const TEST_NOEXCEPT { return val.first | val.second; } +} + +void test() { + test_alloc >(); + test_alloc >(); + + { // Make sure we're allocating/deallocating nodes with the correct allocator + // See https://llvm.org/PR29001 (report is for std::map, but the unordered containers have the same optimization) + class AssertEmpty { + std::vector* lhs_allocs_; + std::vector* rhs_allocs_; + + public: + AssertEmpty(std::vector& lhs_allocs, std::vector& rhs_allocs) + : lhs_allocs_(&lhs_allocs), rhs_allocs_(&rhs_allocs) {} + + void operator()() { + assert(lhs_allocs_->empty()); + assert(rhs_allocs_->empty()); + } }; - std::pair arr[] = { - std::make_pair(1, 2), std::make_pair(2, 3), std::make_pair(3, 4), std::make_pair(4, 5)}; - std::unordered_set, pair_hash> a(arr, arr + 4); - std::unordered_set, pair_hash> b; + std::vector lhs_allocs; + std::vector rhs_allocs; + test_alloc >(lhs_allocs, rhs_allocs, AssertEmpty(lhs_allocs, rhs_allocs)); + } + + { // Ensure that the hasher is copied + { // when the container is non-empty + using Set = std::unordered_set >; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, test_hash(5)); + Set copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.hash_function() == test_hash(5)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.hash_function() == test_hash(5)); + } + { // when the container is empty + using Set = std::unordered_set >; - b = a; - assert(a == b); + const Set orig(0, test_hash(5)); + Set copy; + copy = orig; + assert(copy.empty()); + assert(copy.hash_function() == test_hash(5)); + + // Check that orig is still what is expected + assert(orig.empty()); + assert(orig.hash_function() == test_hash(5)); + } } + { // Ensure that the equality comparator is copied + { // when the container is non-empty + using Set = std::unordered_set, test_equal_to >; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), test_equal_to(23)); + Set copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.key_eq() == test_equal_to(23)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(copy.key_eq() == test_equal_to(23)); + } + { // when the container is empty + using Set = std::unordered_set, test_equal_to >; + + const Set orig(0, std::hash(), test_equal_to(23)); + Set copy; + copy = orig; + assert(copy.size() == 0); + assert(copy.key_eq() == test_equal_to(23)); + + // Check that orig is still what is expected + assert(orig.size() == 0); + assert(copy.key_eq() == test_equal_to(23)); + } + } + + { // Ensure that the max load factor is copied + { // when the container is non-empty + using Set = std::unordered_set; + + int arr[] = {1, 2, 3}; + Set orig(std::begin(arr), std::end(arr)); + orig.max_load_factor(33.f); + Set copy; + copy = orig; + assert(copy.size() == 3); + assert(copy.max_load_factor() == 33.f); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.max_load_factor() == 33.f); + } + { // when the container is empty + using Set = std::unordered_set; + + Set orig; + orig.max_load_factor(17.f); + Set copy; + copy = orig; + assert(copy.size() == 0); + assert(copy.max_load_factor() == 17.f); + + // Check that orig is still what is expected + assert(orig.size() == 0); + assert(orig.max_load_factor() == 17.f); + } + } + + { // Check that pocca is handled properly + { // pocca == true_type + { // when the container is non-empty + using Alloc = other_allocator; + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), Alloc(3)); + Set copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(3)); + } + { // when the container is empty + using Alloc = other_allocator; + using Set = std::unordered_set, std::equal_to, Alloc>; + + const Set orig(Alloc(3)); + Set copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(3)); + } + } + { // pocca == false_type + { // when the container is non-empty + using Alloc = test_allocator; + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), Alloc(3)); + Set copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(1)); + } + { // when the container is empty + using Alloc = test_allocator; + using Set = std::unordered_set, std::equal_to, Alloc>; + + const Set orig(Alloc(3)); + Set copy(Alloc(1)); + copy = orig; + assert(copy.get_allocator() == Alloc(1)); + } + } + } +} + +int main(int, char**) { + test(); + return 0; } diff --git a/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/copy.pass.cpp b/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/copy.pass.cpp index e795d810e9fa1..ff8fa13cffe64 100644 --- a/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/copy.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/copy.pass.cpp @@ -14,84 +14,130 @@ // unordered_set(const unordered_set& u); -#include #include #include #include #include +#include -#include "test_macros.h" #include "../../../test_compare.h" #include "../../../test_hash.h" -#include "test_allocator.h" #include "min_allocator.h" +#include "test_allocator.h" +#include "test_macros.h" -int main(int, char**) { - { - typedef std::unordered_set, test_equal_to, test_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), test_allocator(10)); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.count(1) == 1); - assert(c.count(2) == 1); - assert(c.count(3) == 1); - assert(c.count(4) == 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == test_allocator(10)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +void test_alloc() { + { // Simple check + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr)); + Set copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 3); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(3) == 1); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 3); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(3) == 1); + } + { // single element copy + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1}; + const Set orig(std::begin(arr), std::end(arr)); + Set copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.count(1) == 1); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.count(1) == 1); + } + { // Copy empty map + using Set = std::unordered_set, std::equal_to, Alloc>; + + const Set orig; + Set copy = orig; + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(copy.max_load_factor() == 1.f); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); } -#if TEST_STD_VER >= 11 - { - typedef std::unordered_set, test_equal_to, other_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), other_allocator(10)); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.count(1) == 1); - assert(c.count(2) == 1); - assert(c.count(3) == 1); - assert(c.count(4) == 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == other_allocator(-2)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + { // Ensure that the hash function is copied + using Set = std::unordered_set, std::equal_to, Alloc>; + const Set orig(0, test_hash(23)); + Set copy = orig; + assert(copy.hash_function() == test_hash(23)); + + // Check that orig is still what is expected + assert(orig.hash_function() == test_hash(23)); + } + { // Ensure that the quality comparator is copied + using Set = std::unordered_set, test_equal_to, Alloc>; + const Set orig(0, std::hash(), test_equal_to(56)); + Set copy = orig; + assert(copy.key_eq() == test_equal_to(56)); + + // Check that orig is still what is expected + assert(orig.key_eq() == test_equal_to(56)); } - { - typedef std::unordered_set, test_equal_to, min_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), min_allocator()); - C c = c0; - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.count(1) == 1); - assert(c.count(2) == 1); - assert(c.count(3) == 1); - assert(c.count(4) == 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == min_allocator()); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +} + +void test() { + test_alloc >(); + test_alloc >(); + + { // Ensure that the allocator is copied + using Set = std::unordered_set, std::equal_to, test_allocator >; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), test_allocator(10)); + Set copy = orig; + assert(copy.size() == 3); + assert(copy.get_allocator() == test_allocator(10)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.get_allocator() == test_allocator(10)); + assert(orig.get_allocator().get_id() != test_alloc_base::moved_value); } -#endif + + { // Ensure that soccc is handled properly + using Set = std::unordered_set, std::equal_to, other_allocator >; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr), 0, std::hash(), std::equal_to(), other_allocator(10)); + Set copy = orig; + assert(copy.size() == 3); + assert(copy.get_allocator() == other_allocator(-2)); + + // Check that orig is still what is expected + assert(orig.size() == 3); + assert(orig.get_allocator() == other_allocator(10)); + } +} + +int main(int, char**) { + test(); return 0; } diff --git a/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/copy_alloc.pass.cpp b/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/copy_alloc.pass.cpp index 49a5f9406e1e1..388c5c621db15 100644 --- a/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/copy_alloc.pass.cpp +++ b/libcxx/test/std/containers/unord/unord.set/unord.set.cnstr/copy_alloc.pass.cpp @@ -14,63 +14,107 @@ // unordered_set(const unordered_set& u, const allocator_type& a); -#include #include #include #include #include +#include -#include "test_macros.h" #include "../../../test_compare.h" #include "../../../test_hash.h" -#include "test_allocator.h" #include "min_allocator.h" +#include "test_allocator.h" +#include "test_macros.h" -int main(int, char**) { - { - typedef std::unordered_set, test_equal_to, test_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), test_allocator(10)); - C c(c0, test_allocator(5)); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.count(1) == 1); - assert(c.count(2) == 1); - assert(c.count(3) == 1); - assert(c.count(4) == 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == test_allocator(5)); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); +template +void test_alloc(const Alloc& new_alloc) { + { // Simple check + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1, 2, 3}; + const Set orig(std::begin(arr), std::end(arr)); + Set copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 5); + assert(copy.size() == 3); + assert(copy.count(1) == 1); + assert(copy.count(2) == 1); + assert(copy.count(3) == 1); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 5); + assert(orig.size() == 3); + assert(orig.count(1) == 1); + assert(orig.count(2) == 1); + assert(orig.count(3) == 1); } -#if TEST_STD_VER >= 11 - { - typedef std::unordered_set, test_equal_to, min_allocator > C; - typedef int P; - P a[] = {P(1), P(2), P(3), P(4), P(1), P(2)}; - C c0(a, a + sizeof(a) / sizeof(a[0]), 7, test_hash(8), test_equal_to(9), min_allocator()); - C c(c0, min_allocator()); - LIBCPP_ASSERT(c.bucket_count() == 7); - assert(c.size() == 4); - assert(c.count(1) == 1); - assert(c.count(2) == 1); - assert(c.count(3) == 1); - assert(c.count(4) == 1); - assert(c.hash_function() == test_hash(8)); - assert(c.key_eq() == test_equal_to(9)); - assert(c.get_allocator() == min_allocator()); - assert(!c.empty()); - assert(static_cast(std::distance(c.begin(), c.end())) == c.size()); - assert(static_cast(std::distance(c.cbegin(), c.cend())) == c.size()); - assert(std::fabs(c.load_factor() - (float)c.size() / c.bucket_count()) < FLT_EPSILON); - assert(c.max_load_factor() == 1); + { // single element check + using Set = std::unordered_set, std::equal_to, Alloc>; + + int arr[] = {1}; + const Set orig(std::begin(arr), std::end(arr)); + Set copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 2); + assert(copy.size() == 1); + assert(copy.count(1) == 1); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(std::fabs(copy.load_factor() - static_cast(copy.size()) / copy.bucket_count()) < FLT_EPSILON); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 2); + assert(orig.size() == 1); + assert(orig.count(1) == 1); } -#endif + { // Copy empty map + using Set = std::unordered_set, std::equal_to, Alloc>; + + const Set orig; + Set copy(orig, new_alloc); + LIBCPP_ASSERT(copy.bucket_count() == 0); + assert(copy.size() == 0); + assert(static_cast(std::distance(copy.begin(), copy.end())) == copy.size()); + assert(copy.max_load_factor() == 1.f); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + LIBCPP_ASSERT(orig.bucket_count() == 0); + assert(orig.size() == 0); + } + { // Ensure that the hash function is copied + using Set = std::unordered_set, std::equal_to, Alloc>; + const Set orig(0, test_hash(23)); + Set copy(orig, new_alloc); + assert(copy.hash_function() == test_hash(23)); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + assert(orig.hash_function() == test_hash(23)); + } + { // Ensure that the quality comparator is copied + using Set = std::unordered_set, test_equal_to, Alloc>; + const Set orig(0, std::hash(), test_equal_to(56)); + Set copy(orig, new_alloc); + assert(copy.key_eq() == test_equal_to(56)); + assert(copy.get_allocator() == new_alloc); + + // Check that orig is still what is expected + assert(orig.key_eq() == test_equal_to(56)); + } +} + +void test() { + test_alloc(std::allocator()); + test_alloc(min_allocator()); + test_alloc(test_allocator(25)); +} + +int main(int, char**) { + test(); return 0; }