[libcxx-commits] [libcxx] [libc++] Optimize __hash_table copy constructors and assignment (PR #151951)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Thu Aug 14 00:21:53 PDT 2025


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/151951

>From 5d9de6006c5b72bf8908d7d64ebf0c882bb790e9 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
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 <class _Tp, class _Hash, class _Equal, class _Alloc>
-__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 <class _Tp, class _Hash, class _Equal, class _Alloc>
 __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 <class _Tp, class _Hash, class _Equal, class _Alloc>
-__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<value_type> __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<value_type> __il);
 #  endif // _LIBCPP_CXX03_LANG
 
@@ -1563,12 +1550,6 @@ unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(
   insert(__first, __last);
 }
 
-template <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
-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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
 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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
-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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
 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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
-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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
 inline unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>&
 unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=(initializer_list<value_type> __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<value_type> __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<value_type> __il);
 #  endif // _LIBCPP_CXX03_LANG
 
@@ -2315,13 +2269,6 @@ template <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
 inline unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap(const allocator_type& __a)
     : __table_(typename __table::allocator_type(__a)) {}
 
-template <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
-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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
 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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
-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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
 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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
-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 <class _Key, class _Tp, class _Hash, class _Pred, class _Alloc>
 inline unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>&
 unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=(initializer_list<value_type> __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<value_type> __il);
 #  endif // _LIBCPP_CXX03_LANG
 
@@ -1070,12 +1067,6 @@ unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set(
 template <class _Value, class _Hash, class _Pred, class _Alloc>
 inline unordered_set<_Value, _Hash, _Pred, _Alloc>::unordered_set(const allocator_type& __a) : __table_(__a) {}
 
-template <class _Value, class _Hash, class _Pred, class _Alloc>
-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 <class _Value, class _Hash, class _Pred, class _Alloc>
 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 <class _Value, class _Hash, class _Pred, class _Alloc>
-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 <class _Value, class _Hash, class _Pred, class _Alloc>
 inline unordered_set<_Value, _Hash, _Pred, _Alloc>&
 unordered_set<_Value, _Hash, _Pred, _Alloc>::operator=(initializer_list<value_type> __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<value_type> __il);
 #  endif // _LIBCPP_CXX03_LANG
 
@@ -1685,13 +1665,6 @@ template <class _Value, class _Hash, class _Pred, class _Alloc>
 inline unordered_multiset<_Value, _Hash, _Pred, _Alloc>::unordered_multiset(const allocator_type& __a)
     : __table_(__a) {}
 
-template <class _Value, class _Hash, class _Pred, class _Alloc>
-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 <class _Value, class _Hash, class _Pred, class _Alloc>
 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 <class _Value, class _Hash, class _Pred, class _Alloc>
-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 <class _Value, class _Hash, class _Pred, class _Alloc>
 inline unordered_multiset<_Value, _Hash, _Pred, _Alloc>&
 unordered_multiset<_Value, _Hash, _Pred, _Alloc>::operator=(initializer_list<value_type> __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 <algorithm>
-#include <unordered_map>
-#include <string>
 #include <cassert>
 #include <cfloat>
 #include <cmath>
 #include <cstddef>
+#include <string>
+#include <unordered_map>
+#include <vector>
 
 #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<std::pair<const int, std::string> > A;
-    typedef std::unordered_map<int, std::string, test_hash<int>, test_equal_to<int>, A > C;
-    typedef std::pair<int, std::string> 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<int>(8), test_equal_to<int>(9), A(10));
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A(4));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 T>
+class tracking_allocator {
+  std::vector<void*>* allocs_;
+
+  template <class U>
+  friend class tracking_allocator;
+
+public:
+  using value_type                             = T;
+  using propagate_on_container_copy_assignment = std::true_type;
+
+  tracking_allocator(std::vector<void*>& allocs) : allocs_(&allocs) {}
+
+  template <class U>
+  tracking_allocator(const tracking_allocator<U>& other) : allocs_(other.allocs_) {}
+
+  T* allocate(std::size_t n) {
+    T* allocation = std::allocator<T>().allocate(n);
+    allocs_->push_back(allocation);
+    return allocation;
   }
-  {
-    typedef std::unordered_map<int, std::string> C;
-    typedef std::pair<const int, std::string> 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<T>().deallocate(ptr, n);
   }
-  {
-    typedef other_allocator<std::pair<const int, std::string> > A;
-    typedef std::unordered_map<int, std::string, test_hash<int>, test_equal_to<int>, A > C;
-    typedef std::pair<int, std::string> 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<int>(8), test_equal_to<int>(9), A(10));
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A(10));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc, class AllocatorInvariant = NoOp>
+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<const int, int>;
+      using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), 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<int, int, std::hash<int>, std::equal_to<int>, 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<const int, int>;
+      using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      V arr[] = {V(1, 1)};
+      const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      V arr[] = {V(1, 1)};
+      const Map orig(rhs_alloc);
+      Map copy(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      V arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)};
+      Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+      orig = static_cast<const Map&>(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<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      Map orig(rhs_alloc);
+      orig = static_cast<const Map&>(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<const int, int>;
+      using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), rhs_alloc);
+
+      V rhs_arr[] = {V(10, 4), V(13, 5)};
+      Map copy(begin(rhs_arr), end(rhs_arr), 0, std::hash<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), 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<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), 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<int>(), std::equal_to<int>(), 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<std::pair<const int, std::string> > A;
-    typedef std::unordered_map<int, std::string, test_hash<int>, test_equal_to<int>, A > C;
-    typedef std::pair<int, std::string> 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<std::allocator<std::pair<const int, int> > >();
+  test_alloc<min_allocator<std::pair<const int, int> > >();
+
+  { // 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<void*>* lhs_allocs_;
+      std::vector<void*>* rhs_allocs_;
+
+    public:
+      AssertEmpty(std::vector<void*>& lhs_allocs, std::vector<void*>& 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<int>(8), test_equal_to<int>(9), A());
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A());
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<void*> lhs_allocs;
+    std::vector<void*> rhs_allocs;
+    test_alloc<tracking_allocator<std::pair<const int, int> > >(
+        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<const int, int>;
+      using Map = std::unordered_map<int, int, test_hash<int> >;
+
+      V arr[] = {V(1, 1), V(2, 2), V(3, 3)};
+      const Map orig(begin(arr), end(arr), 0, test_hash<int>(5));
+      Map copy;
+      copy = orig;
+      assert(copy.size() == 3);
+      assert(copy.hash_function() == test_hash<int>(5));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 3);
+      assert(orig.hash_function() == test_hash<int>(5));
+    }
+    { // when the container is empty
+      using Map = std::unordered_map<int, int, test_hash<int> >;
+
+      const Map orig(0, test_hash<int>(5));
+      Map copy;
+      copy = orig;
+      assert(copy.empty());
+      assert(copy.hash_function() == test_hash<int>(5));
+
+      // Check that orig is still what is expected
+      assert(orig.empty());
+      assert(orig.hash_function() == test_hash<int>(5));
+    }
   }
-#endif
+
+  {   // Ensure that the equality comparator is copied
+    { // when the container is non-empty
+      using V   = std::pair<const int, int>;
+      using Map = std::unordered_map<int, int, std::hash<int>, test_equal_to<int> >;
+
+      V arr[] = {V(1, 1), V(2, 2), V(3, 3)};
+      const Map orig(begin(arr), end(arr), 0, std::hash<int>(), test_equal_to<int>(23));
+      Map copy;
+      copy = orig;
+      assert(copy.size() == 3);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 3);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+    }
+    { // when the container is empty
+      using Map = std::unordered_map<int, int, std::hash<int>, test_equal_to<int> >;
+
+      const Map orig(0, std::hash<int>(), test_equal_to<int>(23));
+      Map copy;
+      copy = orig;
+      assert(copy.size() == 0);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 0);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+    }
+  }
+
+  {   // Ensure that the max load factor is copied
+    { // when the container is non-empty
+      using V   = std::pair<const int, int>;
+      using Map = std::unordered_map<int, int>;
+
+      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<int, int>;
+
+      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<const int, int>;
+        using Alloc = other_allocator<V>;
+        using Map   = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+        V arr[] = {V(1, 1), V(2, 2), V(3, 3)};
+        const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), Alloc(3));
+        Map copy(Alloc(1));
+        copy = orig;
+        assert(copy.get_allocator() == Alloc(3));
+      }
+      { // when the container is empty
+        using Alloc = other_allocator<std::pair<const int, int> >;
+        using Map   = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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<const int, int>;
+        using Alloc = test_allocator<V>;
+        using Map   = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+        V arr[] = {V(1, 1), V(2, 2), V(3, 3)};
+        const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), Alloc(3));
+        Map copy(Alloc(1));
+        copy = orig;
+        assert(copy.get_allocator() == Alloc(1));
+      }
+      { // when the container is empty
+        using Alloc = test_allocator<std::pair<const int, int> >;
+        using Map   = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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 <unordered_map>
-#include <string>
 #include <cassert>
 #include <cfloat>
 #include <cmath>
 #include <cstddef>
+#include <unordered_map>
 
-#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<int,
-                               std::string,
-                               test_hash<int>,
-                               test_equal_to<int>,
-                               test_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         test_allocator<std::pair<const int, std::string> >(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (test_allocator<std::pair<const int, std::string> >(10)));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc>
+void test_alloc() {
+  { // Simple check
+    using V   = std::pair<const int, int>;
+    using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<const int, int>;
+    using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+    const Map orig;
+    Map copy = orig;
+    LIBCPP_ASSERT(copy.bucket_count() == 0);
+    assert(copy.size() == 0);
+    assert(static_cast<std::size_t>(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<int,
-                               std::string,
-                               test_hash<int>,
-                               test_equal_to<int>,
-                               other_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         other_allocator<std::pair<const int, std::string> >(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (other_allocator<std::pair<const int, std::string> >(-2)));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int, int, test_hash<int>, std::equal_to<int>, Alloc>;
+    const Map orig(0, test_hash<int>(23));
+    Map copy = orig;
+    assert(copy.hash_function() == test_hash<int>(23));
+
+    // Check that orig is still what is expected
+    assert(orig.hash_function() == test_hash<int>(23));
+  }
+  { // Ensure that the quality comparator is copied
+    using Map = std::unordered_map<int, int, std::hash<int>, test_equal_to<int>, Alloc>;
+    const Map orig(0, std::hash<int>(), test_equal_to<int>(56));
+    Map copy = orig;
+    assert(copy.key_eq() == test_equal_to<int>(56));
+
+    // Check that orig is still what is expected
+    assert(orig.key_eq() == test_equal_to<int>(56));
   }
-  {
-    typedef std::unordered_map<int,
-                               std::string,
-                               test_hash<int>,
-                               test_equal_to<int>,
-                               min_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         min_allocator<std::pair<const int, std::string> >());
-    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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (min_allocator<std::pair<const int, std::string> >()));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<std::allocator<std::pair<const int, int> > >();
+  test_alloc<min_allocator<std::pair<const int, int> > >();
+
+  { // Ensure that the allocator is copied
+    using V   = std::pair<const int, int>;
+    using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, test_allocator<V> >;
+
+    V arr[] = {V(1, 1), V(2, 3), V(3, 6)};
+    const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), test_allocator<V>(10));
+    Map copy = orig;
+    assert(copy.size() == 3);
+    assert(copy.get_allocator() == test_allocator<V>(10));
+
+    // Check that orig is still what is expected
+    assert(orig.size() == 3);
+    assert(orig.get_allocator() == test_allocator<V>(10));
+    assert(orig.get_allocator().get_id() != test_alloc_base::moved_value);
   }
-#endif
+
+  { // Ensure that soccc is handled properly
+    using V   = std::pair<const int, int>;
+    using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, other_allocator<V> >;
+
+    V arr[] = {V(1, 1), V(2, 3), V(3, 6)};
+    const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), other_allocator<V>(10));
+    Map copy = orig;
+    assert(copy.size() == 3);
+    assert(copy.get_allocator() == other_allocator<V>(-2));
+
+    // Check that orig is still what is expected
+    assert(orig.size() == 3);
+    assert(orig.get_allocator() == other_allocator<V>(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 <unordered_map>
-#include <string>
 #include <cassert>
 #include <cfloat>
 #include <cmath>
 #include <cstddef>
+#include <unordered_map>
 
-#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<int,
-                               std::string,
-                               test_hash<int>,
-                               test_equal_to<int>,
-                               test_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         test_allocator<std::pair<const int, std::string> >(10));
-    C c(c0, test_allocator<std::pair<const int, std::string> >(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (test_allocator<std::pair<const int, std::string> >(5)));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc>
+void test_alloc(const Alloc& new_alloc) {
+  { // Simple check
+    using V   = std::pair<const int, int>;
+    using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<const int, int>;
+    using Map = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+    const Map orig;
+    Map copy(orig, new_alloc);
+    LIBCPP_ASSERT(copy.bucket_count() == 0);
+    assert(copy.size() == 0);
+    assert(static_cast<std::size_t>(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<int,
-                               std::string,
-                               test_hash<int>,
-                               test_equal_to<int>,
-                               min_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         min_allocator<std::pair<const int, std::string> >());
-    C c(c0, min_allocator<std::pair<const int, std::string> >());
-    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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (min_allocator<std::pair<const int, std::string> >()));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int, int, test_hash<int>, std::equal_to<int>, Alloc>;
+    const Map orig(0, test_hash<int>(23));
+    Map copy(orig, new_alloc);
+    assert(copy.hash_function() == test_hash<int>(23));
+    assert(copy.get_allocator() == new_alloc);
+
+    // Check that orig is still what is expected
+    assert(orig.hash_function() == test_hash<int>(23));
   }
-  {
-    typedef explicit_allocator<std::pair<const int, std::string>> A;
-    typedef std::unordered_map<int, std::string, test_hash<int>, test_equal_to<int>, A > C;
-    typedef std::pair<int, std::string> 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<int>(8), test_equal_to<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A{});
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int, int, std::hash<int>, test_equal_to<int>, Alloc>;
+    const Map orig(0, std::hash<int>(), test_equal_to<int>(56));
+    Map copy(orig, new_alloc);
+    assert(copy.key_eq() == test_equal_to<int>(56));
+    assert(copy.get_allocator() == new_alloc);
+
+    // Check that orig is still what is expected
+    assert(orig.key_eq() == test_equal_to<int>(56));
   }
-#endif
+}
+
+void test() {
+  test_alloc(std::allocator<std::pair<const int, int> >());
+  test_alloc(min_allocator<std::pair<const int, int> >());
+  test_alloc(test_allocator<std::pair<const int, int> >(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 <unordered_map>
-#include <string>
-#include <set>
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
+#include <algorithm>
 #include <cassert>
 #include <cfloat>
-#include <cmath>
-#include <algorithm>
 #include <cstddef>
+#include <unordered_map>
+#include <vector>
 
-#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<std::pair<const int, std::string> > A;
-    typedef std::unordered_multimap<int, std::string, test_hash<int>, test_equal_to<int>, A > C;
-    typedef std::pair<int, std::string> 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<int>(8), test_equal_to<int>(9), A(10));
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(3), A(4));
-    c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    std::multiset<std::string> s;
-    s.insert("one");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(1), c.end(), 1, s);
-    s.insert("two");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(2), c.end(), 2, s);
-    s.insert("three");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(3), c.end(), 3, s);
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(4), c.end(), 4, s);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A(4));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 T>
+class tracking_allocator {
+  std::vector<void*>* allocs_;
+
+  template <class U>
+  friend class tracking_allocator;
+
+public:
+  using value_type                             = T;
+  using propagate_on_container_copy_assignment = std::true_type;
+
+  tracking_allocator(std::vector<void*>& allocs) : allocs_(&allocs) {}
+
+  template <class U>
+  tracking_allocator(const tracking_allocator<U>& other) : allocs_(other.allocs_) {}
+
+  T* allocate(std::size_t n) {
+    T* allocation = std::allocator<T>().allocate(n);
+    allocs_->push_back(allocation);
+    return allocation;
   }
-  {
-    typedef std::unordered_multimap<int, std::string> C;
-    typedef std::pair<const int, std::string> 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<T>().deallocate(ptr, n);
   }
-  {
-    typedef other_allocator<std::pair<const int, std::string> > A;
-    typedef std::unordered_multimap<int, std::string, test_hash<int>, test_equal_to<int>, A > C;
-    typedef std::pair<int, std::string> 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<int>(8), test_equal_to<int>(9), A(10));
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(3), A(4));
-    c = c0;
-    assert(c.bucket_count() >= 7);
-    assert(c.size() == 6);
-    std::multiset<std::string> s;
-    s.insert("one");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(1), c.end(), 1, s);
-    s.insert("two");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(2), c.end(), 2, s);
-    s.insert("three");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(3), c.end(), 3, s);
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(4), c.end(), 4, s);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A(10));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc, class AllocatorInvariant = NoOp>
+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<const int, int>;
+      using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), 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<int, int, std::hash<int>, std::equal_to<int>, 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<const int, int>;
+      using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      V arr[] = {V(1, 1)};
+      const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      V arr[] = {V(1, 1)};
+      const Map orig(rhs_alloc);
+      Map copy(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      V arr[] = {V(1, 1), V(2, 3), V(4, 4), V(5, 2)};
+      Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+      orig = static_cast<const Map&>(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<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      Map orig(rhs_alloc);
+      orig = static_cast<const Map&>(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<std::pair<const int, std::string> > A;
-    typedef std::unordered_multimap<int, std::string, test_hash<int>, test_equal_to<int>, A > C;
-    typedef std::pair<int, std::string> 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<const int, int>;
+      using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), rhs_alloc);
+
+      V rhs_arr[] = {V(10, 4), V(13, 5)};
+      Map copy(begin(rhs_arr), end(rhs_arr), 0, std::hash<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), 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<int>(), std::equal_to<int>(), 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<const int, int>;
+      using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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<int>(), std::equal_to<int>(), 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<int>(), std::equal_to<int>(), 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<std::allocator<std::pair<const int, int> > >();
+  test_alloc<min_allocator<std::pair<const int, int> > >();
+
+  { // 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<void*>* lhs_allocs_;
+      std::vector<void*>* rhs_allocs_;
+
+    public:
+      AssertEmpty(std::vector<void*>& lhs_allocs, std::vector<void*>& 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<int>(8), test_equal_to<int>(9), A());
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(3), A());
-    c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    std::multiset<std::string> s;
-    s.insert("one");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(1), c.end(), 1, s);
-    s.insert("two");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(2), c.end(), 2, s);
-    s.insert("three");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(3), c.end(), 3, s);
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(4), c.end(), 4, s);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A());
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<void*> lhs_allocs;
+    std::vector<void*> rhs_allocs;
+    test_alloc<tracking_allocator<std::pair<const int, int> > >(
+        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<const int, int>;
+      using Map = std::unordered_multimap<int, int, test_hash<int> >;
+
+      V arr[] = {V(1, 1), V(2, 2), V(3, 3)};
+      const Map orig(begin(arr), end(arr), 0, test_hash<int>(5));
+      Map copy;
+      copy = orig;
+      assert(copy.size() == 3);
+      assert(copy.hash_function() == test_hash<int>(5));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 3);
+      assert(orig.hash_function() == test_hash<int>(5));
+    }
+    { // when the container is empty
+      using Map = std::unordered_multimap<int, int, test_hash<int> >;
+
+      const Map orig(0, test_hash<int>(5));
+      Map copy;
+      copy = orig;
+      assert(copy.empty());
+      assert(copy.hash_function() == test_hash<int>(5));
+
+      // Check that orig is still what is expected
+      assert(orig.empty());
+      assert(orig.hash_function() == test_hash<int>(5));
+    }
   }
-#endif
+
+  {   // Ensure that the equality comparator is copied
+    { // when the container is non-empty
+      using V   = std::pair<const int, int>;
+      using Map = std::unordered_multimap<int, int, std::hash<int>, test_equal_to<int> >;
+
+      V arr[] = {V(1, 1), V(2, 2), V(3, 3)};
+      const Map orig(begin(arr), end(arr), 0, std::hash<int>(), test_equal_to<int>(23));
+      Map copy;
+      copy = orig;
+      assert(copy.size() == 3);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 3);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+    }
+    { // when the container is empty
+      using Map = std::unordered_multimap<int, int, std::hash<int>, test_equal_to<int> >;
+
+      const Map orig(0, std::hash<int>(), test_equal_to<int>(23));
+      Map copy;
+      copy = orig;
+      assert(copy.size() == 0);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 0);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+    }
+  }
+
+  {   // Ensure that the max load factor is copied
+    { // when the container is non-empty
+      using V   = std::pair<const int, int>;
+      using Map = std::unordered_multimap<int, int>;
+
+      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<int, int>;
+
+      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<const int, int>;
+        using Alloc = other_allocator<V>;
+        using Map   = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+        V arr[] = {V(1, 1), V(2, 2), V(3, 3)};
+        const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), Alloc(3));
+        Map copy(Alloc(1));
+        copy = orig;
+        assert(copy.get_allocator() == Alloc(3));
+      }
+      { // when the container is empty
+        using Alloc = other_allocator<std::pair<const int, int> >;
+        using Map   = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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<const int, int>;
+        using Alloc = test_allocator<V>;
+        using Map   = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+        V arr[] = {V(1, 1), V(2, 2), V(3, 3)};
+        const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), Alloc(3));
+        Map copy(Alloc(1));
+        copy = orig;
+        assert(copy.get_allocator() == Alloc(1));
+      }
+      { // when the container is empty
+        using Alloc = test_allocator<std::pair<const int, int> >;
+        using Map   = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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<int,
-                                    std::string,
-                                    test_hash<int>,
-                                    test_equal_to<int>,
-                                    test_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         test_allocator<std::pair<const int, std::string> >(10));
-    C c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    std::multiset<std::string> s;
-    s.insert("one");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(1), c.end(), 1, s);
-    s.insert("two");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(2), c.end(), 2, s);
-    s.insert("three");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(3), c.end(), 3, s);
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(4), c.end(), 4, s);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (test_allocator<std::pair<const int, std::string> >(10)));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc>
+void test_alloc() {
+  { // Simple check
+    using V   = std::pair<const int, int>;
+    using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<const int, int>;
+    using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+    const Map orig;
+    Map copy = orig;
+    LIBCPP_ASSERT(copy.bucket_count() == 0);
+    assert(copy.size() == 0);
+    assert(static_cast<std::size_t>(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<int, int, test_hash<int>, std::equal_to<int>, Alloc>;
+    const Map orig(0, test_hash<int>(23));
+    Map copy = orig;
+    assert(copy.hash_function() == test_hash<int>(23));
+
+    // Check that orig is still what is expected
+    assert(orig.hash_function() == test_hash<int>(23));
+  }
+  { // Ensure that the quality comparator is copied
+    using Map = std::unordered_multimap<int, int, std::hash<int>, test_equal_to<int>, Alloc>;
+    const Map orig(0, std::hash<int>(), test_equal_to<int>(56));
+    Map copy = orig;
+    assert(copy.key_eq() == test_equal_to<int>(56));
+
+    // Check that orig is still what is expected
+    assert(orig.key_eq() == test_equal_to<int>(56));
   }
-#if TEST_STD_VER >= 11
-  {
-    typedef std::unordered_multimap<int,
-                                    std::string,
-                                    test_hash<int>,
-                                    test_equal_to<int>,
-                                    other_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         other_allocator<std::pair<const int, std::string> >(10));
-    C c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    std::multiset<std::string> s;
-    s.insert("one");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(1), c.end(), 1, s);
-    s.insert("two");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(2), c.end(), 2, s);
-    s.insert("three");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(3), c.end(), 3, s);
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(4), c.end(), 4, s);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (other_allocator<std::pair<const int, std::string> >(-2)));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<std::allocator<std::pair<const int, int> > >();
+  test_alloc<min_allocator<std::pair<const int, int> > >();
+
+  { // Ensure that the allocator is copied
+    using V   = std::pair<const int, int>;
+    using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, test_allocator<V> >;
+
+    V arr[] = {V(1, 1), V(2, 3), V(3, 6)};
+    const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), test_allocator<V>(10));
+    Map copy = orig;
+    assert(copy.size() == 3);
+    assert(copy.get_allocator() == test_allocator<V>(10));
+
+    // Check that orig is still what is expected
+    assert(orig.size() == 3);
+    assert(orig.get_allocator() == test_allocator<V>(10));
+    assert(orig.get_allocator().get_id() != test_alloc_base::moved_value);
   }
-  {
-    typedef std::unordered_multimap<int,
-                                    std::string,
-                                    test_hash<int>,
-                                    test_equal_to<int>,
-                                    min_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         min_allocator<std::pair<const int, std::string> >());
-    C c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    std::multiset<std::string> s;
-    s.insert("one");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(1), c.end(), 1, s);
-    s.insert("two");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(2), c.end(), 2, s);
-    s.insert("three");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(3), c.end(), 3, s);
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(4), c.end(), 4, s);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (min_allocator<std::pair<const int, std::string> >()));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<const int, int>;
+    using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, other_allocator<V> >;
+
+    V arr[] = {V(1, 1), V(2, 3), V(3, 6)};
+    const Map orig(begin(arr), end(arr), 0, std::hash<int>(), std::equal_to<int>(), other_allocator<V>(10));
+    Map copy = orig;
+    assert(copy.size() == 3);
+    assert(copy.get_allocator() == other_allocator<V>(-2));
+
+    // Check that orig is still what is expected
+    assert(orig.size() == 3);
+    assert(orig.get_allocator() == other_allocator<V>(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<int,
-                                    std::string,
-                                    test_hash<int>,
-                                    test_equal_to<int>,
-                                    test_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         test_allocator<std::pair<const int, std::string> >(10));
-    C c(c0, test_allocator<std::pair<const int, std::string> >(5));
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    std::multiset<std::string> s;
-    s.insert("one");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(1), c.end(), 1, s);
-    s.insert("two");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(2), c.end(), 2, s);
-    s.insert("three");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(3), c.end(), 3, s);
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(4), c.end(), 4, s);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (test_allocator<std::pair<const int, std::string> >(5)));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc>
+void test_alloc(const Alloc& new_alloc) {
+  { // Simple check
+    using V   = std::pair<const int, int>;
+    using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int,
-                                    std::string,
-                                    test_hash<int>,
-                                    test_equal_to<int>,
-                                    min_allocator<std::pair<const int, std::string> > >
-        C;
-    typedef std::pair<int, std::string> 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<int>(8),
-         test_equal_to<int>(9),
-         min_allocator<std::pair<const int, std::string> >());
-    C c(c0, min_allocator<std::pair<const int, std::string> >());
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    std::multiset<std::string> s;
-    s.insert("one");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(1), c.end(), 1, s);
-    s.insert("two");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(2), c.end(), 2, s);
-    s.insert("three");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(3), c.end(), 3, s);
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(4), c.end(), 4, s);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == (min_allocator<std::pair<const int, std::string> >()));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<const int, int>;
+    using Map = std::unordered_multimap<int, int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<std::pair<const int, std::string>> A;
-    typedef std::unordered_multimap<int, std::string, test_hash<int>, test_equal_to<int>, A > C;
-    typedef std::pair<int, std::string> 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<int>(8), test_equal_to<int>(9), A{});
-    C c(c0, A{});
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    std::multiset<std::string> s;
-    s.insert("one");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(1), c.end(), 1, s);
-    s.insert("two");
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(2), c.end(), 2, s);
-    s.insert("three");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(3), c.end(), 3, s);
-    s.insert("four");
-    CheckConsecutiveKeys<C::const_iterator>(c.find(4), c.end(), 4, s);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A{});
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int, int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+    const Map orig;
+    Map copy(orig, new_alloc);
+    LIBCPP_ASSERT(copy.bucket_count() == 0);
+    assert(copy.size() == 0);
+    assert(static_cast<std::size_t>(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<int, int, test_hash<int>, std::equal_to<int>, Alloc>;
+    const Map orig(0, test_hash<int>(23));
+    Map copy(orig, new_alloc);
+    assert(copy.hash_function() == test_hash<int>(23));
+    assert(copy.get_allocator() == new_alloc);
+
+    // Check that orig is still what is expected
+    assert(orig.hash_function() == test_hash<int>(23));
+  }
+  { // Ensure that the quality comparator is copied
+    using Map = std::unordered_multimap<int, int, std::hash<int>, test_equal_to<int>, Alloc>;
+    const Map orig(0, std::hash<int>(), test_equal_to<int>(56));
+    Map copy(orig, new_alloc);
+    assert(copy.key_eq() == test_equal_to<int>(56));
+    assert(copy.get_allocator() == new_alloc);
+
+    // Check that orig is still what is expected
+    assert(orig.key_eq() == test_equal_to<int>(56));
   }
-#endif
+}
+
+void test() {
+  test_alloc(std::allocator<std::pair<const int, int> >());
+  test_alloc(min_allocator<std::pair<const int, int> >());
+  test_alloc(test_allocator<std::pair<const int, int> >(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 <unordered_set>
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
 #include <algorithm>
 #include <cassert>
 #include <cfloat>
 #include <cmath>
 #include <cstddef>
+#include <unordered_set>
+#include <vector>
 
-#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<int> A;
-    typedef std::unordered_multiset<int, test_hash<int>, test_equal_to<int>, 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<int>(8), test_equal_to<int>(9), A(10));
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(3), A(4));
-    c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    CheckConsecutiveValues<C::const_iterator>(c.find(1), c.end(), 1, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(2), c.end(), 2, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(3), c.end(), 3, 1);
-    CheckConsecutiveValues<C::const_iterator>(c.find(4), c.end(), 4, 1);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A(4));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 T>
+class tracking_allocator {
+  std::vector<void*>* allocs_;
+
+  template <class U>
+  friend class tracking_allocator;
+
+public:
+  using value_type                             = T;
+  using propagate_on_container_copy_assignment = std::true_type;
+
+  tracking_allocator(std::vector<void*>& allocs) : allocs_(&allocs) {}
+
+  template <class U>
+  tracking_allocator(const tracking_allocator<U>& other) : allocs_(other.allocs_) {}
+
+  T* allocate(std::size_t n) {
+    T* allocation = std::allocator<T>().allocate(n);
+    allocs_->push_back(allocation);
+    return allocation;
   }
-  {
-    typedef std::unordered_multiset<int> 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<T>().deallocate(ptr, n);
   }
-  {
-    typedef other_allocator<int> A;
-    typedef std::unordered_multiset<int, test_hash<int>, test_equal_to<int>, 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<int>(8), test_equal_to<int>(9), A(10));
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(3), A(4));
-    c = c0;
-    assert(c.bucket_count() >= 7);
-    assert(c.size() == 6);
-    CheckConsecutiveValues<C::const_iterator>(c.find(1), c.end(), 1, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(2), c.end(), 2, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(3), c.end(), 3, 1);
-    CheckConsecutiveValues<C::const_iterator>(c.find(4), c.end(), 4, 1);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A(10));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int> A;
-    typedef std::unordered_multiset<int, test_hash<int>, test_equal_to<int>, 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<int>(8), test_equal_to<int>(9), A());
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(3), A());
-    c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    CheckConsecutiveValues<C::const_iterator>(c.find(1), c.end(), 1, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(2), c.end(), 2, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(3), c.end(), 3, 1);
-    CheckConsecutiveValues<C::const_iterator>(c.find(4), c.end(), 4, 1);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A());
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc, class AllocatorInvariant = NoOp>
+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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1, 2, 4, 4};
+      const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1, 2, 4, 4};
+      const Set orig(rhs_alloc);
+      Set copy(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1};
+      const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1};
+      const Set orig(rhs_alloc);
+      Set copy(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1, 2, 4, 5};
+      Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+      orig = static_cast<const Set&>(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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      Set orig(rhs_alloc);
+      orig = static_cast<const Set&>(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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int lhs_arr[] = {1, 2, 4, 5};
+      const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+
+      int rhs_arr[] = {10, 13};
+      Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int lhs_arr[] = {1, 2, 4, 5};
+      const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+
+      int rhs_arr[] = {10, 13, 12, 0};
+      Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int lhs_arr[] = {1, 2, 4, 5};
+      const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+
+      int rhs_arr[] = {10, 13, 12, 0, 50, 2};
+      Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash<int>(), std::equal_to<int>(), 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<std::allocator<int> >();
+  test_alloc<min_allocator<int> >();
+
+  { // 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<void*>* lhs_allocs_;
+      std::vector<void*>* rhs_allocs_;
+
+    public:
+      AssertEmpty(std::vector<void*>& lhs_allocs, std::vector<void*>& rhs_allocs)
+          : lhs_allocs_(&lhs_allocs), rhs_allocs_(&rhs_allocs) {}
+
+      void operator()() {
+        assert(lhs_allocs_->empty());
+        assert(rhs_allocs_->empty());
+      }
+    };
+
+    std::vector<void*> lhs_allocs;
+    std::vector<void*> rhs_allocs;
+    test_alloc<tracking_allocator<int> >(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, test_hash<int> >;
+
+      int arr[] = {1, 2, 3};
+      const Set orig(std::begin(arr), std::end(arr), 0, test_hash<int>(5));
+      Set copy;
+      copy = orig;
+      assert(copy.size() == 3);
+      assert(copy.hash_function() == test_hash<int>(5));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 3);
+      assert(orig.hash_function() == test_hash<int>(5));
+    }
+    { // when the container is empty
+      using Set = std::unordered_multiset<int, test_hash<int> >;
+
+      const Set orig(0, test_hash<int>(5));
+      Set copy;
+      copy = orig;
+      assert(copy.empty());
+      assert(copy.hash_function() == test_hash<int>(5));
+
+      // Check that orig is still what is expected
+      assert(orig.empty());
+      assert(orig.hash_function() == test_hash<int>(5));
+    }
+  }
+
+  {   // Ensure that the equality comparator is copied
+    { // when the container is non-empty
+      using Set = std::unordered_multiset<int, std::hash<int>, test_equal_to<int> >;
+
+      int arr[] = {1, 2, 3};
+      const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), test_equal_to<int>(23));
+      Set copy;
+      copy = orig;
+      assert(copy.size() == 3);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 3);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+    }
+    { // when the container is empty
+      using Set = std::unordered_multiset<int, std::hash<int>, test_equal_to<int> >;
+
+      const Set orig(0, std::hash<int>(), test_equal_to<int>(23));
+      Set copy;
+      copy = orig;
+      assert(copy.size() == 0);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 0);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+    }
+  }
+
+  {   // Ensure that the max load factor is copied
+    { // when the container is non-empty
+      using Set = std::unordered_multiset<int>;
+
+      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<int>;
+
+      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<int>;
+        using Set   = std::unordered_multiset<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+        int arr[] = {1, 2, 3};
+        const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), Alloc(3));
+        Set copy(Alloc(1));
+        copy = orig;
+        assert(copy.get_allocator() == Alloc(3));
+      }
+      { // when the container is empty
+        using Alloc = other_allocator<int>;
+        using Set   = std::unordered_multiset<int, std::hash<int>, std::equal_to<int>, 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<int>;
+        using Set   = std::unordered_multiset<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+        int arr[] = {1, 2, 3};
+        const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), Alloc(3));
+        Set copy(Alloc(1));
+        copy = orig;
+        assert(copy.get_allocator() == Alloc(1));
+      }
+      { // when the container is empty
+        using Alloc = test_allocator<int>;
+        using Set   = std::unordered_multiset<int, std::hash<int>, std::equal_to<int>, 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<int, test_hash<int>, test_equal_to<int>, test_allocator<int> > 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<int>(8), test_equal_to<int>(9), test_allocator<int>(10));
-    C c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    CheckConsecutiveValues<C::const_iterator>(c.find(1), c.end(), 1, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(2), c.end(), 2, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(3), c.end(), 3, 1);
-    CheckConsecutiveValues<C::const_iterator>(c.find(4), c.end(), 4, 1);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == test_allocator<int>(10));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc>
+void test_alloc() {
+  { // Simple check
+    using Set = std::unordered_multiset<int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+    const Set orig;
+    Set copy = orig;
+    LIBCPP_ASSERT(copy.bucket_count() == 0);
+    assert(copy.size() == 0);
+    assert(static_cast<std::size_t>(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<int, test_hash<int>, std::equal_to<int>, Alloc>;
+    const Set orig(0, test_hash<int>(23));
+    Set copy = orig;
+    assert(copy.hash_function() == test_hash<int>(23));
+
+    // Check that orig is still what is expected
+    assert(orig.hash_function() == test_hash<int>(23));
+  }
+  { // Ensure that the quality comparator is copied
+    using Set = std::unordered_multiset<int, std::hash<int>, test_equal_to<int>, Alloc>;
+    const Set orig(0, std::hash<int>(), test_equal_to<int>(56));
+    Set copy = orig;
+    assert(copy.key_eq() == test_equal_to<int>(56));
+
+    // Check that orig is still what is expected
+    assert(orig.key_eq() == test_equal_to<int>(56));
   }
-#if TEST_STD_VER >= 11
-  {
-    typedef std::unordered_multiset<int, test_hash<int>, test_equal_to<int>, other_allocator<int> > 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<int>(8), test_equal_to<int>(9), other_allocator<int>(10));
-    C c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    CheckConsecutiveValues<C::const_iterator>(c.find(1), c.end(), 1, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(2), c.end(), 2, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(3), c.end(), 3, 1);
-    CheckConsecutiveValues<C::const_iterator>(c.find(4), c.end(), 4, 1);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == other_allocator<int>(-2));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<std::allocator<int> >();
+  test_alloc<min_allocator<int> >();
+
+  { // Ensure that the allocator is copied
+    using Set = std::unordered_multiset<int, std::hash<int>, std::equal_to<int>, test_allocator<int> >;
+
+    int arr[] = {1, 2, 3};
+    const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), test_allocator<int>(10));
+    Set copy = orig;
+    assert(copy.size() == 3);
+    assert(copy.get_allocator() == test_allocator<int>(10));
+
+    // Check that orig is still what is expected
+    assert(orig.size() == 3);
+    assert(orig.get_allocator() == test_allocator<int>(10));
+    assert(orig.get_allocator().get_id() != test_alloc_base::moved_value);
   }
-  {
-    typedef std::unordered_multiset<int, test_hash<int>, test_equal_to<int>, min_allocator<int> > 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<int>(8), test_equal_to<int>(9), min_allocator<int>());
-    C c = c0;
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    CheckConsecutiveValues<C::const_iterator>(c.find(1), c.end(), 1, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(2), c.end(), 2, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(3), c.end(), 3, 1);
-    CheckConsecutiveValues<C::const_iterator>(c.find(4), c.end(), 4, 1);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == min_allocator<int>());
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int, std::hash<int>, std::equal_to<int>, other_allocator<int> >;
+
+    int arr[] = {1, 2, 3};
+    const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), other_allocator<int>(10));
+    Set copy = orig;
+    assert(copy.size() == 3);
+    assert(copy.get_allocator() == other_allocator<int>(-2));
+
+    // Check that orig is still what is expected
+    assert(orig.size() == 3);
+    assert(orig.get_allocator() == other_allocator<int>(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 <unordered_set>
 #include <cassert>
 #include <cfloat>
 #include <cmath>
 #include <cstddef>
+#include <unordered_set>
 
-#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<int, test_hash<int>, test_equal_to<int>, test_allocator<int> > 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<int>(8), test_equal_to<int>(9), test_allocator<int>(10));
-    C c(c0, test_allocator<int>(5));
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    CheckConsecutiveValues<C::const_iterator>(c.find(1), c.end(), 1, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(2), c.end(), 2, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(3), c.end(), 3, 1);
-    CheckConsecutiveValues<C::const_iterator>(c.find(4), c.end(), 4, 1);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == test_allocator<int>(5));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc>
+void test_alloc(const Alloc& new_alloc) {
+  { // Simple check
+    using Set = std::unordered_multiset<int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, test_hash<int>, test_equal_to<int>, min_allocator<int> > 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<int>(8), test_equal_to<int>(9), min_allocator<int>());
-    C c(c0, min_allocator<int>());
-    LIBCPP_ASSERT(c.bucket_count() == 7);
-    assert(c.size() == 6);
-    CheckConsecutiveValues<C::const_iterator>(c.find(1), c.end(), 1, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(2), c.end(), 2, 2);
-    CheckConsecutiveValues<C::const_iterator>(c.find(3), c.end(), 3, 1);
-    CheckConsecutiveValues<C::const_iterator>(c.find(4), c.end(), 4, 1);
-    assert(c.hash_function() == test_hash<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == min_allocator<int>());
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+    const Set orig;
+    Set copy(orig, new_alloc);
+    LIBCPP_ASSERT(copy.bucket_count() == 0);
+    assert(copy.size() == 0);
+    assert(static_cast<std::size_t>(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<int, test_hash<int>, std::equal_to<int>, Alloc>;
+    const Set orig(0, test_hash<int>(23));
+    Set copy(orig, new_alloc);
+    assert(copy.hash_function() == test_hash<int>(23));
+    assert(copy.get_allocator() == new_alloc);
+
+    // Check that orig is still what is expected
+    assert(orig.hash_function() == test_hash<int>(23));
+  }
+  { // Ensure that the quality comparator is copied
+    using Set = std::unordered_multiset<int, std::hash<int>, test_equal_to<int>, Alloc>;
+    const Set orig(0, std::hash<int>(), test_equal_to<int>(56));
+    Set copy(orig, new_alloc);
+    assert(copy.key_eq() == test_equal_to<int>(56));
+    assert(copy.get_allocator() == new_alloc);
+
+    // Check that orig is still what is expected
+    assert(orig.key_eq() == test_equal_to<int>(56));
+  }
+}
+
+void test() {
+  test_alloc(std::allocator<int>());
+  test_alloc(min_allocator<int>());
+  test_alloc(test_allocator<int>(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 <unordered_set>
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
 #include <algorithm>
 #include <cassert>
 #include <cfloat>
 #include <cmath>
 #include <cstddef>
+#include <unordered_set>
 #include <utility>
+#include <vector>
 
-#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<int> A;
-    typedef std::unordered_set<int, test_hash<int>, test_equal_to<int>, 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<int>(8), test_equal_to<int>(9), A(10));
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A(4));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 T>
+class tracking_allocator {
+  std::vector<void*>* allocs_;
+
+  template <class U>
+  friend class tracking_allocator;
+
+public:
+  using value_type                             = T;
+  using propagate_on_container_copy_assignment = std::true_type;
+
+  tracking_allocator(std::vector<void*>& allocs) : allocs_(&allocs) {}
+
+  template <class U>
+  tracking_allocator(const tracking_allocator<U>& other) : allocs_(other.allocs_) {}
+
+  T* allocate(std::size_t n) {
+    T* allocation = std::allocator<T>().allocate(n);
+    allocs_->push_back(allocation);
+    return allocation;
   }
-  {
-    typedef std::unordered_set<int> 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<T>().deallocate(ptr, n);
   }
-  {
-    typedef other_allocator<int> A;
-    typedef std::unordered_set<int, test_hash<int>, test_equal_to<int>, 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<int>(8), test_equal_to<int>(9), A(10));
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A(10));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int> A;
-    typedef std::unordered_set<int, test_hash<int>, test_equal_to<int>, 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<int>(8), test_equal_to<int>(9), A());
-    C c(a, a + 2, 7, test_hash<int>(2), test_equal_to<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == A());
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc, class AllocatorInvariant = NoOp>
+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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1, 2, 4, 5};
+      const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1, 2, 4, 5};
+      const Set orig(rhs_alloc);
+      Set copy(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1};
+      const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1};
+      const Set orig(rhs_alloc);
+      Set copy(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int arr[] = {1, 2, 4, 5};
+      Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+      orig = static_cast<const Set&>(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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      Set orig(rhs_alloc);
+      orig = static_cast<const Set&>(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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int lhs_arr[] = {1, 2, 4, 5};
+      const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+
+      int rhs_arr[] = {10, 13};
+      Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int lhs_arr[] = {1, 2, 4, 5};
+      const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+
+      int rhs_arr[] = {10, 13, 12, 0};
+      Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+      int lhs_arr[] = {1, 2, 4, 5};
+      const Set orig(std::begin(lhs_arr), std::end(lhs_arr), 0, std::hash<int>(), std::equal_to<int>(), rhs_alloc);
+
+      int rhs_arr[] = {10, 13, 12, 0, 50, 2};
+      Set copy(std::begin(rhs_arr), std::end(rhs_arr), 0, std::hash<int>(), std::equal_to<int>(), 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<int, int> val) const TEST_NOEXCEPT { return val.first | val.second; }
+}
+
+void test() {
+  test_alloc<std::allocator<int> >();
+  test_alloc<min_allocator<int> >();
+
+  { // 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<void*>* lhs_allocs_;
+      std::vector<void*>* rhs_allocs_;
+
+    public:
+      AssertEmpty(std::vector<void*>& lhs_allocs, std::vector<void*>& rhs_allocs)
+          : lhs_allocs_(&lhs_allocs), rhs_allocs_(&rhs_allocs) {}
+
+      void operator()() {
+        assert(lhs_allocs_->empty());
+        assert(rhs_allocs_->empty());
+      }
     };
 
-    std::pair<int, int> arr[] = {
-        std::make_pair(1, 2), std::make_pair(2, 3), std::make_pair(3, 4), std::make_pair(4, 5)};
-    std::unordered_set<std::pair<int, int>, pair_hash> a(arr, arr + 4);
-    std::unordered_set<std::pair<int, int>, pair_hash> b;
+    std::vector<void*> lhs_allocs;
+    std::vector<void*> rhs_allocs;
+    test_alloc<tracking_allocator<int> >(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, test_hash<int> >;
+
+      int arr[] = {1, 2, 3};
+      const Set orig(std::begin(arr), std::end(arr), 0, test_hash<int>(5));
+      Set copy;
+      copy = orig;
+      assert(copy.size() == 3);
+      assert(copy.hash_function() == test_hash<int>(5));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 3);
+      assert(orig.hash_function() == test_hash<int>(5));
+    }
+    { // when the container is empty
+      using Set = std::unordered_set<int, test_hash<int> >;
 
-    b = a;
-    assert(a == b);
+      const Set orig(0, test_hash<int>(5));
+      Set copy;
+      copy = orig;
+      assert(copy.empty());
+      assert(copy.hash_function() == test_hash<int>(5));
+
+      // Check that orig is still what is expected
+      assert(orig.empty());
+      assert(orig.hash_function() == test_hash<int>(5));
+    }
   }
 
+  {   // Ensure that the equality comparator is copied
+    { // when the container is non-empty
+      using Set = std::unordered_set<int, std::hash<int>, test_equal_to<int> >;
+
+      int arr[] = {1, 2, 3};
+      const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), test_equal_to<int>(23));
+      Set copy;
+      copy = orig;
+      assert(copy.size() == 3);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 3);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+    }
+    { // when the container is empty
+      using Set = std::unordered_set<int, std::hash<int>, test_equal_to<int> >;
+
+      const Set orig(0, std::hash<int>(), test_equal_to<int>(23));
+      Set copy;
+      copy = orig;
+      assert(copy.size() == 0);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+
+      // Check that orig is still what is expected
+      assert(orig.size() == 0);
+      assert(copy.key_eq() == test_equal_to<int>(23));
+    }
+  }
+
+  {   // Ensure that the max load factor is copied
+    { // when the container is non-empty
+      using Set = std::unordered_set<int>;
+
+      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<int>;
+
+      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<int>;
+        using Set   = std::unordered_set<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+        int arr[] = {1, 2, 3};
+        const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), Alloc(3));
+        Set copy(Alloc(1));
+        copy = orig;
+        assert(copy.get_allocator() == Alloc(3));
+      }
+      { // when the container is empty
+        using Alloc = other_allocator<int>;
+        using Set   = std::unordered_set<int, std::hash<int>, std::equal_to<int>, 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<int>;
+        using Set   = std::unordered_set<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+        int arr[] = {1, 2, 3};
+        const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), Alloc(3));
+        Set copy(Alloc(1));
+        copy = orig;
+        assert(copy.get_allocator() == Alloc(1));
+      }
+      { // when the container is empty
+        using Alloc = test_allocator<int>;
+        using Set   = std::unordered_set<int, std::hash<int>, std::equal_to<int>, 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 <unordered_set>
 #include <cassert>
 #include <cfloat>
 #include <cmath>
 #include <cstddef>
+#include <unordered_set>
 
-#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<int, test_hash<int>, test_equal_to<int>, test_allocator<int> > 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<int>(8), test_equal_to<int>(9), test_allocator<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == test_allocator<int>(10));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc>
+void test_alloc() {
+  { // Simple check
+    using Set = std::unordered_set<int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+    const Set orig;
+    Set copy = orig;
+    LIBCPP_ASSERT(copy.bucket_count() == 0);
+    assert(copy.size() == 0);
+    assert(static_cast<std::size_t>(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<int, test_hash<int>, test_equal_to<int>, other_allocator<int> > 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<int>(8), test_equal_to<int>(9), other_allocator<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == other_allocator<int>(-2));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int, test_hash<int>, std::equal_to<int>, Alloc>;
+    const Set orig(0, test_hash<int>(23));
+    Set copy = orig;
+    assert(copy.hash_function() == test_hash<int>(23));
+
+    // Check that orig is still what is expected
+    assert(orig.hash_function() == test_hash<int>(23));
+  }
+  { // Ensure that the quality comparator is copied
+    using Set = std::unordered_set<int, std::hash<int>, test_equal_to<int>, Alloc>;
+    const Set orig(0, std::hash<int>(), test_equal_to<int>(56));
+    Set copy = orig;
+    assert(copy.key_eq() == test_equal_to<int>(56));
+
+    // Check that orig is still what is expected
+    assert(orig.key_eq() == test_equal_to<int>(56));
   }
-  {
-    typedef std::unordered_set<int, test_hash<int>, test_equal_to<int>, min_allocator<int> > 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<int>(8), test_equal_to<int>(9), min_allocator<int>());
-    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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == min_allocator<int>());
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<std::allocator<int> >();
+  test_alloc<min_allocator<int> >();
+
+  { // Ensure that the allocator is copied
+    using Set = std::unordered_set<int, std::hash<int>, std::equal_to<int>, test_allocator<int> >;
+
+    int arr[] = {1, 2, 3};
+    const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), test_allocator<int>(10));
+    Set copy = orig;
+    assert(copy.size() == 3);
+    assert(copy.get_allocator() == test_allocator<int>(10));
+
+    // Check that orig is still what is expected
+    assert(orig.size() == 3);
+    assert(orig.get_allocator() == test_allocator<int>(10));
+    assert(orig.get_allocator().get_id() != test_alloc_base::moved_value);
   }
-#endif
+
+  { // Ensure that soccc is handled properly
+    using Set = std::unordered_set<int, std::hash<int>, std::equal_to<int>, other_allocator<int> >;
+
+    int arr[] = {1, 2, 3};
+    const Set orig(std::begin(arr), std::end(arr), 0, std::hash<int>(), std::equal_to<int>(), other_allocator<int>(10));
+    Set copy = orig;
+    assert(copy.size() == 3);
+    assert(copy.get_allocator() == other_allocator<int>(-2));
+
+    // Check that orig is still what is expected
+    assert(orig.size() == 3);
+    assert(orig.get_allocator() == other_allocator<int>(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 <unordered_set>
 #include <cassert>
 #include <cfloat>
 #include <cmath>
 #include <cstddef>
+#include <unordered_set>
 
-#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<int, test_hash<int>, test_equal_to<int>, test_allocator<int> > 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<int>(8), test_equal_to<int>(9), test_allocator<int>(10));
-    C c(c0, test_allocator<int>(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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == test_allocator<int>(5));
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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 <class Alloc>
+void test_alloc(const Alloc& new_alloc) {
+  { // Simple check
+    using Set = std::unordered_set<int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, test_hash<int>, test_equal_to<int>, min_allocator<int> > 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<int>(8), test_equal_to<int>(9), min_allocator<int>());
-    C c(c0, min_allocator<int>());
-    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<int>(8));
-    assert(c.key_eq() == test_equal_to<int>(9));
-    assert(c.get_allocator() == min_allocator<int>());
-    assert(!c.empty());
-    assert(static_cast<std::size_t>(std::distance(c.begin(), c.end())) == c.size());
-    assert(static_cast<std::size_t>(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<int, std::hash<int>, std::equal_to<int>, 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::size_t>(std::distance(copy.begin(), copy.end())) == copy.size());
+    assert(std::fabs(copy.load_factor() - static_cast<float>(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<int, std::hash<int>, std::equal_to<int>, Alloc>;
+
+    const Set orig;
+    Set copy(orig, new_alloc);
+    LIBCPP_ASSERT(copy.bucket_count() == 0);
+    assert(copy.size() == 0);
+    assert(static_cast<std::size_t>(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<int, test_hash<int>, std::equal_to<int>, Alloc>;
+    const Set orig(0, test_hash<int>(23));
+    Set copy(orig, new_alloc);
+    assert(copy.hash_function() == test_hash<int>(23));
+    assert(copy.get_allocator() == new_alloc);
+
+    // Check that orig is still what is expected
+    assert(orig.hash_function() == test_hash<int>(23));
+  }
+  { // Ensure that the quality comparator is copied
+    using Set = std::unordered_set<int, std::hash<int>, test_equal_to<int>, Alloc>;
+    const Set orig(0, std::hash<int>(), test_equal_to<int>(56));
+    Set copy(orig, new_alloc);
+    assert(copy.key_eq() == test_equal_to<int>(56));
+    assert(copy.get_allocator() == new_alloc);
+
+    // Check that orig is still what is expected
+    assert(orig.key_eq() == test_equal_to<int>(56));
+  }
+}
+
+void test() {
+  test_alloc(std::allocator<int>());
+  test_alloc(min_allocator<int>());
+  test_alloc(test_allocator<int>(25));
+}
+
+int main(int, char**) {
+  test();
 
   return 0;
 }



More information about the libcxx-commits mailing list