public inbox for libstdc++@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
@ 2021-10-13 17:10 François Dumont
  2021-10-14  8:23 ` Jonathan Wakely
  0 siblings, 1 reply; 21+ messages in thread
From: François Dumont @ 2021-10-13 17:10 UTC (permalink / raw)
  To: libstdc++; +Cc: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 5204 bytes --]

Hi

     libstdc++: [_GLIBCXX_DEBUG] Implement unordered container merge

     The _GLIBCXX_DEBUG unordered containers need a dedicated merge 
implementation
     so that any existing iterator on the transfered nodes is properly 
invalidated.

     Add typedef/using declaration for everything used as-is from normal 
implementation.

     libstdc++-v3/ChangeLog:

             * include/debug/safe_container.h (_Safe_container<>): Make 
all methods
             protected.
             * include/debug/safe_unordered_container.h
             (_Safe_unordered_container<>::_M_invalide_all): Make public.
             (_Safe_unordered_container<>::_M_invalide_if): Likewise.
(_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
             * include/debug/unordered_map
             (unordered_map<>::mapped_type, pointer, const_pointer): New 
typedef.
             (unordered_map<>::reference, const_reference, 
difference_type): New typedef.
             (unordered_map<>::get_allocator, empty, size, max_size): 
Add usings.
             (unordered_map<>::bucket_count, max_bucket_count, bucket): 
Add usings.
             (unordered_map<>::hash_function, key_equal, count, 
contains): Add usings.
             (unordered_map<>::operator[], at, rehash, reserve): Add usings.
             (unordered_map<>::merge): New.
             (unordered_multimap<>::mapped_type, pointer, 
const_pointer): New typedef.
             (unordered_multimap<>::reference, const_reference, 
difference_type): New typedef.
             (unordered_multimap<>::get_allocator, empty, size, 
max_size): Add usings.
             (unordered_multimap<>::bucket_count, max_bucket_count, 
bucket): Add usings.
             (unordered_multimap<>::hash_function, key_equal, count, 
contains): Add usings.
             (unordered_multimap<>::rehash, reserve): Add usings.
             (unordered_multimap<>::merge): New.
             * include/debug/unordered_set
             (unordered_set<>::mapped_type, pointer, const_pointer): New 
typedef.
             (unordered_set<>::reference, const_reference, 
difference_type): New typedef.
             (unordered_set<>::get_allocator, empty, size, max_size): 
Add usings.
             (unordered_set<>::bucket_count, max_bucket_count, bucket): 
Add usings.
             (unordered_set<>::hash_function, key_equal, count, 
contains): Add usings.
             (unordered_set<>::rehash, reserve): Add usings.
             (unordered_set<>::merge): New.
             (unordered_multiset<>::mapped_type, pointer, 
const_pointer): New typedef.
             (unordered_multiset<>::reference, const_reference, 
difference_type): New typedef.
             (unordered_multiset<>::get_allocator, empty, size, 
max_size): Add usings.
             (unordered_multiset<>::bucket_count, max_bucket_count, 
bucket): Add usings.
             (unordered_multiset<>::hash_function, key_equal, count, 
contains): Add usings.
             (unordered_multiset<>::rehash, reserve): Add usings.
             (unordered_multiset<>::merge): New.
             * 
testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New test.
             * 
testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New test.
             * 
testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New test.
             * 
testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc: New test.
             * 
testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New test.
             * 
testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New test.
             * 
testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New test.
             * 
testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New test.
             * testsuite/util/testsuite_abi.h: [_GLIBCXX_DEBUG] Use 
normal unordered container implementation.

Tested under Linux x86_64.

Ok to commit ?

François


[-- Attachment #2: merge.patch --]
[-- Type: text/x-patch, Size: 30413 bytes --]

diff --git a/libstdc++-v3/include/debug/safe_container.h b/libstdc++-v3/include/debug/safe_container.h
index 97c47167fe8..5de55d69f34 100644
--- a/libstdc++-v3/include/debug/safe_container.h
+++ b/libstdc++-v3/include/debug/safe_container.h
@@ -78,7 +78,6 @@ namespace __gnu_debug
       { }
 #endif
 
-    public:
       // Copy assignment invalidate all iterators.
       _Safe_container&
       operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT
diff --git a/libstdc++-v3/include/debug/safe_unordered_container.h b/libstdc++-v3/include/debug/safe_unordered_container.h
index aae1e2dab60..06d0e91282c 100644
--- a/libstdc++-v3/include/debug/safe_unordered_container.h
+++ b/libstdc++-v3/include/debug/safe_unordered_container.h
@@ -72,6 +72,7 @@ namespace __gnu_debug
 		{ return __it != __local_end; });
       }
 
+    public:
       void
       _M_invalidate_all()
       {
diff --git a/libstdc++-v3/include/debug/unordered_map b/libstdc++-v3/include/debug/unordered_map
index bb697d364ea..d359d285f99 100644
--- a/libstdc++-v3/include/debug/unordered_map
+++ b/libstdc++-v3/include/debug/unordered_map
@@ -97,7 +97,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_map>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -106,6 +111,7 @@ namespace __debug
 	_Base_local_iterator, unordered_map>		local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_map>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_map() = default;
 
@@ -209,6 +215,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_map& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -291,6 +302,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -298,6 +313,8 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -538,9 +555,72 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __size = __source.size();
+	  _Base::merge(__source._M_base());
+	  if (__source.size() == __size)
+	    return;
+
+	  if (__source.empty())
+	    __source._M_invalidate_all();
+	  else
+	    {
+	      auto __pred = [&__source](auto __it)
+			    { return __source.count(__it->first) == 0; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    }
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __size = __source.size();
+	  _Base::merge(__source._M_base());
+	  if (__source.size() == __size)
+	    return;
+
+	  if (__source.empty())
+	    __source._M_invalidate_all();
+	  else
+	    {
+	      _GLIBCXX_STD_C::unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&
+		__base_src = __source;
+	      auto __pred = [&__base_src](auto __it)
+		{
+		  auto __rng = __base_src.equal_range(__it->first);
+		  for (auto __rit = __rng.first; __rit != __rng.second; ++__rit)
+		    {
+		      if (__it == __rit)
+			return false;
+		    }
+
+		  return true;
+		};
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    }
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -567,6 +647,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -605,6 +690,9 @@ namespace __debug
 	}
 #endif
 
+      using _Base::operator[];
+      using _Base::at;
+
       size_type
       erase(const key_type& __key)
       {
@@ -651,6 +739,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept	{ return *this; }
 
@@ -843,7 +934,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multimap>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -852,6 +948,7 @@ namespace __debug
 	_Base_local_iterator, unordered_multimap>	local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_multimap>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_multimap() = default;
 
@@ -952,6 +1049,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multimap& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -1034,6 +1136,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -1192,9 +1298,37 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _Base::merge(__source._M_base());
+	  __source._M_invalidate_all();
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _Base::merge(__source._M_base());
+	  __source._M_invalidate_all();
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1221,6 +1355,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -1309,6 +1448,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept { return *this; }
 
diff --git a/libstdc++-v3/include/debug/unordered_set b/libstdc++-v3/include/debug/unordered_set
index c25910946b7..10f44903b8f 100644
--- a/libstdc++-v3/include/debug/unordered_set
+++ b/libstdc++-v3/include/debug/unordered_set
@@ -88,6 +88,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -95,6 +96,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_set>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -203,6 +208,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_set& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -285,6 +295,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -292,6 +305,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -303,6 +319,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	std::pair<iterator, bool>
 	emplace(_Args&&... __args)
@@ -423,9 +442,71 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __size = __source.size();
+	  _Base::merge(__source._M_base());
+	  if (__source.size() == __size)
+	    return;
+
+	  if (__source.empty())
+	    __source._M_invalidate_all();
+	  else
+	    {
+	      auto __pred = [&__source](auto __it)
+			    { return __source.count(*__it) == 0; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    }
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __size = __source.size();
+	  _Base::merge(__source._M_base());
+	  if (__source.size() == __size)
+	    return;
+
+	  if (__source.empty())
+	    __source._M_invalidate_all();
+	  else
+	    {
+	      _GLIBCXX_STD_C::unordered_multiset<_Value, _H2, _P2, _Alloc>&
+		__base_src = __source;
+	      auto __pred = [&__base_src](auto __it)
+		{
+		  auto __rng = __base_src.equal_range(*__it);
+		  for (auto __rit = __rng.first; __rit != __rng.second; ++__rit)
+		    {
+		      if (__it == __rit)
+			return false;
+		    }
+
+		  return true;
+		};
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    }
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -452,6 +533,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -707,6 +794,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -714,6 +802,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multiset>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -822,6 +914,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multiset& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -904,6 +1001,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -911,6 +1011,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -922,6 +1025,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	iterator
 	emplace(_Args&&... __args)
@@ -1037,9 +1143,36 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _Base::merge(__source._M_base());
+	  __source._M_invalidate_all();
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _Base::merge(__source._M_base());
+	  __source._M_invalidate_all();
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1066,6 +1199,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
new file mode 100644
index 00000000000..69e8a6741a8
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it4 != it2 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
new file mode 100644
index 00000000000..543cd960a5e
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
new file mode 100644
index 00000000000..8e234799cbf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
new file mode 100644
index 00000000000..3c9c8268f8c
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
new file mode 100644
index 00000000000..25b3b9e0c75
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
new file mode 100644
index 00000000000..8d28d83b972
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
new file mode 100644
index 00000000000..5db91a27ca0
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
new file mode 100644
index 00000000000..a1636703569
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
new file mode 100644
index 00000000000..bce8da7f6cf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
new file mode 100644
index 00000000000..72317a32e89
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
new file mode 100644
index 00000000000..1b1f4870dd1
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
new file mode 100644
index 00000000000..5005cf8468a
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
new file mode 100644
index 00000000000..8a2bc6e468f
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
new file mode 100644
index 00000000000..3ac96540770
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
new file mode 100644
index 00000000000..7e93b55d507
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
new file mode 100644
index 00000000000..14c8ff63b05
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.h b/libstdc++-v3/testsuite/util/testsuite_abi.h
index 667c46c33d3..4a0cf64f6fb 100644
--- a/libstdc++-v3/testsuite/util/testsuite_abi.h
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
@@ -24,7 +24,11 @@
 #include <locale>
 #if __cplusplus >= 201103L
 # include <unordered_map>
+# ifdef _GLIBCXX_DEBUG
+namespace unord = std::_GLIBCXX_STD_C;
+# else
 namespace unord = std;
+# endif
 #else
 # include <tr1/unordered_map>
 namespace unord = std::tr1;

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-10-13 17:10 [PATH][_GLIBCXX_DEBUG] Fix unordered container merge François Dumont
@ 2021-10-14  8:23 ` Jonathan Wakely
  2021-10-16 13:47   ` François Dumont
  0 siblings, 1 reply; 21+ messages in thread
From: Jonathan Wakely @ 2021-10-14  8:23 UTC (permalink / raw)
  To: François Dumont; +Cc: libstdc++, gcc-patches

On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
>
> Hi
>
>      libstdc++: [_GLIBCXX_DEBUG] Implement unordered container merge
>
>      The _GLIBCXX_DEBUG unordered containers need a dedicated merge
> implementation
>      so that any existing iterator on the transfered nodes is properly
> invalidated.
>
>      Add typedef/using declaration for everything used as-is from normal
> implementation.
>
>      libstdc++-v3/ChangeLog:
>
>              * include/debug/safe_container.h (_Safe_container<>): Make
> all methods
>              protected.
>              * include/debug/safe_unordered_container.h
>              (_Safe_unordered_container<>::_M_invalide_all): Make public.
>              (_Safe_unordered_container<>::_M_invalide_if): Likewise.
> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>              * include/debug/unordered_map
>              (unordered_map<>::mapped_type, pointer, const_pointer): New
> typedef.
>              (unordered_map<>::reference, const_reference,
> difference_type): New typedef.
>              (unordered_map<>::get_allocator, empty, size, max_size):
> Add usings.
>              (unordered_map<>::bucket_count, max_bucket_count, bucket):
> Add usings.
>              (unordered_map<>::hash_function, key_equal, count,
> contains): Add usings.
>              (unordered_map<>::operator[], at, rehash, reserve): Add usings.
>              (unordered_map<>::merge): New.
>              (unordered_multimap<>::mapped_type, pointer,
> const_pointer): New typedef.
>              (unordered_multimap<>::reference, const_reference,
> difference_type): New typedef.
>              (unordered_multimap<>::get_allocator, empty, size,
> max_size): Add usings.
>              (unordered_multimap<>::bucket_count, max_bucket_count,
> bucket): Add usings.
>              (unordered_multimap<>::hash_function, key_equal, count,
> contains): Add usings.
>              (unordered_multimap<>::rehash, reserve): Add usings.
>              (unordered_multimap<>::merge): New.
>              * include/debug/unordered_set
>              (unordered_set<>::mapped_type, pointer, const_pointer): New
> typedef.
>              (unordered_set<>::reference, const_reference,
> difference_type): New typedef.
>              (unordered_set<>::get_allocator, empty, size, max_size):
> Add usings.
>              (unordered_set<>::bucket_count, max_bucket_count, bucket):
> Add usings.
>              (unordered_set<>::hash_function, key_equal, count,
> contains): Add usings.
>              (unordered_set<>::rehash, reserve): Add usings.
>              (unordered_set<>::merge): New.
>              (unordered_multiset<>::mapped_type, pointer,
> const_pointer): New typedef.
>              (unordered_multiset<>::reference, const_reference,
> difference_type): New typedef.
>              (unordered_multiset<>::get_allocator, empty, size,
> max_size): Add usings.
>              (unordered_multiset<>::bucket_count, max_bucket_count,
> bucket): Add usings.
>              (unordered_multiset<>::hash_function, key_equal, count,
> contains): Add usings.
>              (unordered_multiset<>::rehash, reserve): Add usings.
>              (unordered_multiset<>::merge): New.
>              *
> testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New test.
>              *
> testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New test.
>              * testsuite/util/testsuite_abi.h: [_GLIBCXX_DEBUG] Use
> normal unordered container implementation.
>
> Tested under Linux x86_64.
>
> Ok to commit ?

Yes, thanks. But ...

This looks like an improvement over what we have now, but not 100%
correct. The merge functions can exit via exception (if any hash
function or equality predicate throws), and if that happens the safe
iterators will not get invalidated. I think we need to call
_Base::merge in a try-block, and do the iterator invalidation whether
we return normally or via an exception.

Something like:

  template<typename _H2, typename _P2>
    void
    merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
    {
      struct _Guard
      {
        _Guard(unordered_set& __source) noexcept
        : _M_source(__source), _M_size(__source.size())
        { }

        ~_Guard()
        {
          const size_type __size = _M_source.size();
          if (__size != _M_size)
            {
              if (__size == 0)
                _M_source._M_invalidate_all();
              else
                {
                  auto __pred = [&__source](auto __it)
                                { return __source.count(*__it) == 0; };
                  __source._M_invalidate_if(__pred);
                  __source._M_invalidate_local_if(__pred);
                }
            }
        }

        _Guard(const _Guard&) = delete;

        unordered_set& _M_source;
        const size_type _M_size;
      };
      _Guard __guard(__source);
      _Base::merge(__source._M_base());
    }


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-10-14  8:23 ` Jonathan Wakely
@ 2021-10-16 13:47   ` François Dumont
  2021-10-16 14:52     ` Jonathan Wakely
  0 siblings, 1 reply; 21+ messages in thread
From: François Dumont @ 2021-10-16 13:47 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: libstdc++, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 6981 bytes --]

Hi

     Here is the new proposal. My only concern is that we are also using 
hash or equal_to functors in the guard destructor.

     I am going to enhance merge normal implementation to make use of 
the cached hash code when hash functors are the same between the source 
and destination of nodes. Maybe I'll be able to make use of it in Debug 
implementation too.

François


On 14/10/21 10:23 am, Jonathan Wakely wrote:
> On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
> <libstdc++@gcc.gnu.org> wrote:
>> Hi
>>
>>       libstdc++: [_GLIBCXX_DEBUG] Implement unordered container merge
>>
>>       The _GLIBCXX_DEBUG unordered containers need a dedicated merge
>> implementation
>>       so that any existing iterator on the transfered nodes is properly
>> invalidated.
>>
>>       Add typedef/using declaration for everything used as-is from normal
>> implementation.
>>
>>       libstdc++-v3/ChangeLog:
>>
>>               * include/debug/safe_container.h (_Safe_container<>): Make
>> all methods
>>               protected.
>>               * include/debug/safe_unordered_container.h
>>               (_Safe_unordered_container<>::_M_invalide_all): Make public.
>>               (_Safe_unordered_container<>::_M_invalide_if): Likewise.
>> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>>               * include/debug/unordered_map
>>               (unordered_map<>::mapped_type, pointer, const_pointer): New
>> typedef.
>>               (unordered_map<>::reference, const_reference,
>> difference_type): New typedef.
>>               (unordered_map<>::get_allocator, empty, size, max_size):
>> Add usings.
>>               (unordered_map<>::bucket_count, max_bucket_count, bucket):
>> Add usings.
>>               (unordered_map<>::hash_function, key_equal, count,
>> contains): Add usings.
>>               (unordered_map<>::operator[], at, rehash, reserve): Add usings.
>>               (unordered_map<>::merge): New.
>>               (unordered_multimap<>::mapped_type, pointer,
>> const_pointer): New typedef.
>>               (unordered_multimap<>::reference, const_reference,
>> difference_type): New typedef.
>>               (unordered_multimap<>::get_allocator, empty, size,
>> max_size): Add usings.
>>               (unordered_multimap<>::bucket_count, max_bucket_count,
>> bucket): Add usings.
>>               (unordered_multimap<>::hash_function, key_equal, count,
>> contains): Add usings.
>>               (unordered_multimap<>::rehash, reserve): Add usings.
>>               (unordered_multimap<>::merge): New.
>>               * include/debug/unordered_set
>>               (unordered_set<>::mapped_type, pointer, const_pointer): New
>> typedef.
>>               (unordered_set<>::reference, const_reference,
>> difference_type): New typedef.
>>               (unordered_set<>::get_allocator, empty, size, max_size):
>> Add usings.
>>               (unordered_set<>::bucket_count, max_bucket_count, bucket):
>> Add usings.
>>               (unordered_set<>::hash_function, key_equal, count,
>> contains): Add usings.
>>               (unordered_set<>::rehash, reserve): Add usings.
>>               (unordered_set<>::merge): New.
>>               (unordered_multiset<>::mapped_type, pointer,
>> const_pointer): New typedef.
>>               (unordered_multiset<>::reference, const_reference,
>> difference_type): New typedef.
>>               (unordered_multiset<>::get_allocator, empty, size,
>> max_size): Add usings.
>>               (unordered_multiset<>::bucket_count, max_bucket_count,
>> bucket): Add usings.
>>               (unordered_multiset<>::hash_function, key_equal, count,
>> contains): Add usings.
>>               (unordered_multiset<>::rehash, reserve): Add usings.
>>               (unordered_multiset<>::merge): New.
>>               *
>> testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New test.
>>               *
>> testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New test.
>>               * testsuite/util/testsuite_abi.h: [_GLIBCXX_DEBUG] Use
>> normal unordered container implementation.
>>
>> Tested under Linux x86_64.
>>
>> Ok to commit ?
> Yes, thanks. But ...
>
> This looks like an improvement over what we have now, but not 100%
> correct. The merge functions can exit via exception (if any hash
> function or equality predicate throws), and if that happens the safe
> iterators will not get invalidated. I think we need to call
> _Base::merge in a try-block, and do the iterator invalidation whether
> we return normally or via an exception.
>
> Something like:
>
>    template<typename _H2, typename _P2>
>      void
>      merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
>      {
>        struct _Guard
>        {
>          _Guard(unordered_set& __source) noexcept
>          : _M_source(__source), _M_size(__source.size())
>          { }
>
>          ~_Guard()
>          {
>            const size_type __size = _M_source.size();
>            if (__size != _M_size)
>              {
>                if (__size == 0)
>                  _M_source._M_invalidate_all();
>                else
>                  {
>                    auto __pred = [&__source](auto __it)
>                                  { return __source.count(*__it) == 0; };
>                    __source._M_invalidate_if(__pred);
>                    __source._M_invalidate_local_if(__pred);
>                  }
>              }
>          }
>
>          _Guard(const _Guard&) = delete;
>
>          unordered_set& _M_source;
>          const size_type _M_size;
>        };
>        _Guard __guard(__source);
>        _Base::merge(__source._M_base());
>      }
>


[-- Attachment #2: merge.patch --]
[-- Type: text/x-patch, Size: 31765 bytes --]

diff --git a/libstdc++-v3/include/debug/safe_container.h b/libstdc++-v3/include/debug/safe_container.h
index 97c47167fe8..5de55d69f34 100644
--- a/libstdc++-v3/include/debug/safe_container.h
+++ b/libstdc++-v3/include/debug/safe_container.h
@@ -78,7 +78,6 @@ namespace __gnu_debug
       { }
 #endif
 
-    public:
       // Copy assignment invalidate all iterators.
       _Safe_container&
       operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT
diff --git a/libstdc++-v3/include/debug/safe_unordered_container.h b/libstdc++-v3/include/debug/safe_unordered_container.h
index aae1e2dab60..06d0e91282c 100644
--- a/libstdc++-v3/include/debug/safe_unordered_container.h
+++ b/libstdc++-v3/include/debug/safe_unordered_container.h
@@ -72,6 +72,7 @@ namespace __gnu_debug
 		{ return __it != __local_end; });
       }
 
+    public:
       void
       _M_invalidate_all()
       {
diff --git a/libstdc++-v3/include/debug/unordered_map b/libstdc++-v3/include/debug/unordered_map
index bb697d364ea..218de6106ab 100644
--- a/libstdc++-v3/include/debug/unordered_map
+++ b/libstdc++-v3/include/debug/unordered_map
@@ -97,7 +97,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_map>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -106,6 +111,7 @@ namespace __debug
 	_Base_local_iterator, unordered_map>		local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_map>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_map() = default;
 
@@ -209,6 +215,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_map& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -291,6 +302,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -298,6 +313,8 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -538,9 +555,113 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+    private:
+      template<typename _Source>
+	struct _UMapGuard
+	{
+	  _UMapGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMapGuard(const _UMapGuard&) = delete;
+
+	  ~_UMapGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size != _M_size)
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      { return _M_source.count(__it->second) == 0; };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+	};
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _UMapGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+    private:
+      template<typename _Source>
+	struct _UMMapGuard
+	{
+	  _UMMapGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMMapGuard(const _UMMapGuard&) = delete;
+
+	  ~_UMMapGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size != _M_size)
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      {
+			auto __rng =
+			  _M_source._M_base().equal_range(__it->first);
+			for (auto __rit = __rng.first;
+			     __rit != __rng.second; ++__rit)
+			  {
+			    if (__it == __rit)
+			      return false;
+			  }
+
+			return true;
+		      };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+        };
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _UMMapGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -567,6 +688,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -605,6 +731,9 @@ namespace __debug
 	}
 #endif
 
+      using _Base::operator[];
+      using _Base::at;
+
       size_type
       erase(const key_type& __key)
       {
@@ -651,6 +780,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept	{ return *this; }
 
@@ -843,7 +975,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multimap>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -852,6 +989,7 @@ namespace __debug
 	_Base_local_iterator, unordered_multimap>	local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_multimap>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_multimap() = default;
 
@@ -952,6 +1090,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multimap& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -1034,6 +1177,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -1192,9 +1339,37 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _Base::merge(__source._M_base());
+	  __source._M_invalidate_all();
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _Base::merge(__source._M_base());
+	  __source._M_invalidate_all();
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1221,6 +1396,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -1309,6 +1489,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept { return *this; }
 
diff --git a/libstdc++-v3/include/debug/unordered_set b/libstdc++-v3/include/debug/unordered_set
index c25910946b7..22b714083a0 100644
--- a/libstdc++-v3/include/debug/unordered_set
+++ b/libstdc++-v3/include/debug/unordered_set
@@ -88,6 +88,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -95,6 +96,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_set>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -203,6 +208,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_set& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -285,6 +295,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -292,6 +305,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -303,6 +319,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	std::pair<iterator, bool>
 	emplace(_Args&&... __args)
@@ -423,9 +442,111 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+    private:
+      template<typename _Source>
+	struct _USetGuard
+	{
+	  _USetGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _USetGuard(const _USetGuard&) = delete;
+
+	  ~_USetGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size != _M_size)
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred = [this](auto __it)
+				  { return _M_source.count(*__it) == 0; };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+	};
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _USetGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+    private:
+      template<typename _Source>
+	struct _UMSetGuard
+	{
+	  _UMSetGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMSetGuard(const _UMSetGuard&) = delete;
+
+	  ~_UMSetGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size != _M_size)
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      {
+			auto __rng = _M_source._M_base().equal_range(*__it);
+			for (auto __rit = __rng.first;
+			     __rit != __rng.second; ++__rit)
+			  {
+			    if (__it == __rit)
+			      return false;
+			  }
+
+			return true;
+		      };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+	};
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _UMSetGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -452,6 +573,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -707,6 +834,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -714,6 +842,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multiset>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -822,6 +954,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multiset& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -904,6 +1041,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -911,6 +1051,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -922,6 +1065,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	iterator
 	emplace(_Args&&... __args)
@@ -1037,9 +1183,36 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _Base::merge(__source._M_base());
+	  __source._M_invalidate_all();
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _Base::merge(__source._M_base());
+	  __source._M_invalidate_all();
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1066,6 +1239,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
new file mode 100644
index 00000000000..69e8a6741a8
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it4 != it2 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
new file mode 100644
index 00000000000..543cd960a5e
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
new file mode 100644
index 00000000000..8e234799cbf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
new file mode 100644
index 00000000000..3c9c8268f8c
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
new file mode 100644
index 00000000000..25b3b9e0c75
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
new file mode 100644
index 00000000000..8d28d83b972
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
new file mode 100644
index 00000000000..5db91a27ca0
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
new file mode 100644
index 00000000000..a1636703569
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
new file mode 100644
index 00000000000..bce8da7f6cf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
new file mode 100644
index 00000000000..72317a32e89
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
new file mode 100644
index 00000000000..1b1f4870dd1
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
new file mode 100644
index 00000000000..5005cf8468a
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
new file mode 100644
index 00000000000..8a2bc6e468f
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
new file mode 100644
index 00000000000..3ac96540770
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
new file mode 100644
index 00000000000..7e93b55d507
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
new file mode 100644
index 00000000000..14c8ff63b05
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.h b/libstdc++-v3/testsuite/util/testsuite_abi.h
index 667c46c33d3..4a0cf64f6fb 100644
--- a/libstdc++-v3/testsuite/util/testsuite_abi.h
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
@@ -24,7 +24,11 @@
 #include <locale>
 #if __cplusplus >= 201103L
 # include <unordered_map>
+# ifdef _GLIBCXX_DEBUG
+namespace unord = std::_GLIBCXX_STD_C;
+# else
 namespace unord = std;
+# endif
 #else
 # include <tr1/unordered_map>
 namespace unord = std::tr1;

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-10-16 13:47   ` François Dumont
@ 2021-10-16 14:52     ` Jonathan Wakely
  2021-10-21 16:51       ` François Dumont
  2021-10-25 18:08       ` François Dumont
  0 siblings, 2 replies; 21+ messages in thread
From: Jonathan Wakely @ 2021-10-16 14:52 UTC (permalink / raw)
  To: François Dumont; +Cc: Jonathan Wakely, libstdc++, gcc-patches

On Sat, 16 Oct 2021, 14:49 François Dumont via Libstdc++, <
libstdc++@gcc.gnu.org> wrote:

> Hi
>
>      Here is the new proposal. My only concern is that we are also using
> hash or equal_to functors in the guard destructor.
>


Can we catch any exception there, invalidate all iterators, and not rethrow
the exception?


>      I am going to enhance merge normal implementation to make use of
> the cached hash code when hash functors are the same between the source
> and destination of nodes. Maybe I'll be able to make use of it in Debug
> implementation too.
>
> François
>
>
> On 14/10/21 10:23 am, Jonathan Wakely wrote:
> > On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
> > <libstdc++@gcc.gnu.org> wrote:
> >> Hi
> >>
> >>       libstdc++: [_GLIBCXX_DEBUG] Implement unordered container merge
> >>
> >>       The _GLIBCXX_DEBUG unordered containers need a dedicated merge
> >> implementation
> >>       so that any existing iterator on the transfered nodes is properly
> >> invalidated.
> >>
> >>       Add typedef/using declaration for everything used as-is from
> normal
> >> implementation.
> >>
> >>       libstdc++-v3/ChangeLog:
> >>
> >>               * include/debug/safe_container.h (_Safe_container<>): Make
> >> all methods
> >>               protected.
> >>               * include/debug/safe_unordered_container.h
> >>               (_Safe_unordered_container<>::_M_invalide_all): Make
> public.
> >>               (_Safe_unordered_container<>::_M_invalide_if): Likewise.
> >> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
> >>               * include/debug/unordered_map
> >>               (unordered_map<>::mapped_type, pointer, const_pointer):
> New
> >> typedef.
> >>               (unordered_map<>::reference, const_reference,
> >> difference_type): New typedef.
> >>               (unordered_map<>::get_allocator, empty, size, max_size):
> >> Add usings.
> >>               (unordered_map<>::bucket_count, max_bucket_count, bucket):
> >> Add usings.
> >>               (unordered_map<>::hash_function, key_equal, count,
> >> contains): Add usings.
> >>               (unordered_map<>::operator[], at, rehash, reserve): Add
> usings.
> >>               (unordered_map<>::merge): New.
> >>               (unordered_multimap<>::mapped_type, pointer,
> >> const_pointer): New typedef.
> >>               (unordered_multimap<>::reference, const_reference,
> >> difference_type): New typedef.
> >>               (unordered_multimap<>::get_allocator, empty, size,
> >> max_size): Add usings.
> >>               (unordered_multimap<>::bucket_count, max_bucket_count,
> >> bucket): Add usings.
> >>               (unordered_multimap<>::hash_function, key_equal, count,
> >> contains): Add usings.
> >>               (unordered_multimap<>::rehash, reserve): Add usings.
> >>               (unordered_multimap<>::merge): New.
> >>               * include/debug/unordered_set
> >>               (unordered_set<>::mapped_type, pointer, const_pointer):
> New
> >> typedef.
> >>               (unordered_set<>::reference, const_reference,
> >> difference_type): New typedef.
> >>               (unordered_set<>::get_allocator, empty, size, max_size):
> >> Add usings.
> >>               (unordered_set<>::bucket_count, max_bucket_count, bucket):
> >> Add usings.
> >>               (unordered_set<>::hash_function, key_equal, count,
> >> contains): Add usings.
> >>               (unordered_set<>::rehash, reserve): Add usings.
> >>               (unordered_set<>::merge): New.
> >>               (unordered_multiset<>::mapped_type, pointer,
> >> const_pointer): New typedef.
> >>               (unordered_multiset<>::reference, const_reference,
> >> difference_type): New typedef.
> >>               (unordered_multiset<>::get_allocator, empty, size,
> >> max_size): Add usings.
> >>               (unordered_multiset<>::bucket_count, max_bucket_count,
> >> bucket): Add usings.
> >>               (unordered_multiset<>::hash_function, key_equal, count,
> >> contains): Add usings.
> >>               (unordered_multiset<>::rehash, reserve): Add usings.
> >>               (unordered_multiset<>::merge): New.
> >>               *
> >> testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New test.
> >>               *
> >> testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New test.
> >>               *
> >> testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New test.
> >>               *
> >> testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New test.
> >>               *
> >> testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc: New
> test.
> >>               *
> >> testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc: New
> test.
> >>               *
> >> testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc: New
> test.
> >>               *
> >> testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc: New
> test.
> >>               *
> >> testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc: New
> test.
> >>               *
> >> testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc: New
> test.
> >>               *
> >> testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc: New
> test.
> >>               *
> >> testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc: New
> test.
> >>               *
> >> testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New test.
> >>               *
> >> testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New test.
> >>               *
> >> testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New test.
> >>               *
> >> testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New test.
> >>               * testsuite/util/testsuite_abi.h: [_GLIBCXX_DEBUG] Use
> >> normal unordered container implementation.
> >>
> >> Tested under Linux x86_64.
> >>
> >> Ok to commit ?
> > Yes, thanks. But ...
> >
> > This looks like an improvement over what we have now, but not 100%
> > correct. The merge functions can exit via exception (if any hash
> > function or equality predicate throws), and if that happens the safe
> > iterators will not get invalidated. I think we need to call
> > _Base::merge in a try-block, and do the iterator invalidation whether
> > we return normally or via an exception.
> >
> > Something like:
> >
> >    template<typename _H2, typename _P2>
> >      void
> >      merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
> >      {
> >        struct _Guard
> >        {
> >          _Guard(unordered_set& __source) noexcept
> >          : _M_source(__source), _M_size(__source.size())
> >          { }
> >
> >          ~_Guard()
> >          {
> >            const size_type __size = _M_source.size();
> >            if (__size != _M_size)
> >              {
> >                if (__size == 0)
> >                  _M_source._M_invalidate_all();
> >                else
> >                  {
> >                    auto __pred = [&__source](auto __it)
> >                                  { return __source.count(*__it) == 0; };
> >                    __source._M_invalidate_if(__pred);
> >                    __source._M_invalidate_local_if(__pred);
> >                  }
> >              }
> >          }
> >
> >          _Guard(const _Guard&) = delete;
> >
> >          unordered_set& _M_source;
> >          const size_type _M_size;
> >        };
> >        _Guard __guard(__source);
> >        _Base::merge(__source._M_base());
> >      }
> >
>
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-10-16 14:52     ` Jonathan Wakely
@ 2021-10-21 16:51       ` François Dumont
  2021-10-21 16:55         ` Jonathan Wakely
  2021-10-25 18:08       ` François Dumont
  1 sibling, 1 reply; 21+ messages in thread
From: François Dumont @ 2021-10-21 16:51 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Jonathan Wakely, libstdc++, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 9288 bytes --]

I eventually would like to propose a different approach.

I am adding a hook in normal implementation to let the _GLIBCXX_DEBUG 
code know when a node is being extracted. This way invalidation is only 
done by comparing nodes, no need to compute hash code for this operation.

The only drawback is that for each extraction we have a linear research 
on iterators to invalidate the correct one. I will implement next an 
optimization when hasher/equal_to are noexcept.

This patch also remove the invalid noexcept qualification on the 
_Hashtable merge methods and make use of const_iterator as it is what is 
expected by the extract.

Tested under Linux x86_64.

Ok to commit ?

François


On 16/10/21 4:52 pm, Jonathan Wakely wrote:
>
>
> On Sat, 16 Oct 2021, 14:49 François Dumont via Libstdc++, 
> <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>
>     Hi
>
>          Here is the new proposal. My only concern is that we are also
>     using
>     hash or equal_to functors in the guard destructor.
>
>
>
> Can we catch any exception there, invalidate all iterators, and not 
> rethrow the exception?
>
>
>          I am going to enhance merge normal implementation to make use of
>     the cached hash code when hash functors are the same between the
>     source
>     and destination of nodes. Maybe I'll be able to make use of it in
>     Debug
>     implementation too.
>
>     François
>
>
>     On 14/10/21 10:23 am, Jonathan Wakely wrote:
>     > On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
>     > <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>     >> Hi
>     >>
>     >>       libstdc++: [_GLIBCXX_DEBUG] Implement unordered container
>     merge
>     >>
>     >>       The _GLIBCXX_DEBUG unordered containers need a dedicated
>     merge
>     >> implementation
>     >>       so that any existing iterator on the transfered nodes is
>     properly
>     >> invalidated.
>     >>
>     >>       Add typedef/using declaration for everything used as-is
>     from normal
>     >> implementation.
>     >>
>     >>       libstdc++-v3/ChangeLog:
>     >>
>     >>               * include/debug/safe_container.h
>     (_Safe_container<>): Make
>     >> all methods
>     >>               protected.
>     >>               * include/debug/safe_unordered_container.h
>     >>  (_Safe_unordered_container<>::_M_invalide_all): Make public.
>     >>  (_Safe_unordered_container<>::_M_invalide_if): Likewise.
>     >> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>     >>               * include/debug/unordered_map
>     >>  (unordered_map<>::mapped_type, pointer, const_pointer): New
>     >> typedef.
>     >>               (unordered_map<>::reference, const_reference,
>     >> difference_type): New typedef.
>     >>  (unordered_map<>::get_allocator, empty, size, max_size):
>     >> Add usings.
>     >>  (unordered_map<>::bucket_count, max_bucket_count, bucket):
>     >> Add usings.
>     >>  (unordered_map<>::hash_function, key_equal, count,
>     >> contains): Add usings.
>     >>               (unordered_map<>::operator[], at, rehash,
>     reserve): Add usings.
>     >>               (unordered_map<>::merge): New.
>     >>  (unordered_multimap<>::mapped_type, pointer,
>     >> const_pointer): New typedef.
>     >>  (unordered_multimap<>::reference, const_reference,
>     >> difference_type): New typedef.
>     >>  (unordered_multimap<>::get_allocator, empty, size,
>     >> max_size): Add usings.
>     >>  (unordered_multimap<>::bucket_count, max_bucket_count,
>     >> bucket): Add usings.
>     >>  (unordered_multimap<>::hash_function, key_equal, count,
>     >> contains): Add usings.
>     >>  (unordered_multimap<>::rehash, reserve): Add usings.
>     >>  (unordered_multimap<>::merge): New.
>     >>               * include/debug/unordered_set
>     >>  (unordered_set<>::mapped_type, pointer, const_pointer): New
>     >> typedef.
>     >>               (unordered_set<>::reference, const_reference,
>     >> difference_type): New typedef.
>     >>  (unordered_set<>::get_allocator, empty, size, max_size):
>     >> Add usings.
>     >>  (unordered_set<>::bucket_count, max_bucket_count, bucket):
>     >> Add usings.
>     >>  (unordered_set<>::hash_function, key_equal, count,
>     >> contains): Add usings.
>     >>               (unordered_set<>::rehash, reserve): Add usings.
>     >>               (unordered_set<>::merge): New.
>     >>  (unordered_multiset<>::mapped_type, pointer,
>     >> const_pointer): New typedef.
>     >>  (unordered_multiset<>::reference, const_reference,
>     >> difference_type): New typedef.
>     >>  (unordered_multiset<>::get_allocator, empty, size,
>     >> max_size): Add usings.
>     >>  (unordered_multiset<>::bucket_count, max_bucket_count,
>     >> bucket): Add usings.
>     >>  (unordered_multiset<>::hash_function, key_equal, count,
>     >> contains): Add usings.
>     >>  (unordered_multiset<>::rehash, reserve): Add usings.
>     >>  (unordered_multiset<>::merge): New.
>     >>               *
>     >> testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New
>     test.
>     >>               * testsuite/util/testsuite_abi.h:
>     [_GLIBCXX_DEBUG] Use
>     >> normal unordered container implementation.
>     >>
>     >> Tested under Linux x86_64.
>     >>
>     >> Ok to commit ?
>     > Yes, thanks. But ...
>     >
>     > This looks like an improvement over what we have now, but not 100%
>     > correct. The merge functions can exit via exception (if any hash
>     > function or equality predicate throws), and if that happens the safe
>     > iterators will not get invalidated. I think we need to call
>     > _Base::merge in a try-block, and do the iterator invalidation
>     whether
>     > we return normally or via an exception.
>     >
>     > Something like:
>     >
>     >    template<typename _H2, typename _P2>
>     >      void
>     >      merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
>     >      {
>     >        struct _Guard
>     >        {
>     >          _Guard(unordered_set& __source) noexcept
>     >          : _M_source(__source), _M_size(__source.size())
>     >          { }
>     >
>     >          ~_Guard()
>     >          {
>     >            const size_type __size = _M_source.size();
>     >            if (__size != _M_size)
>     >              {
>     >                if (__size == 0)
>     >                  _M_source._M_invalidate_all();
>     >                else
>     >                  {
>     >                    auto __pred = [&__source](auto __it)
>     >                                  { return __source.count(*__it)
>     == 0; };
>     >                    __source._M_invalidate_if(__pred);
>     > __source._M_invalidate_local_if(__pred);
>     >                  }
>     >              }
>     >          }
>     >
>     >          _Guard(const _Guard&) = delete;
>     >
>     >          unordered_set& _M_source;
>     >          const size_type _M_size;
>     >        };
>     >        _Guard __guard(__source);
>     >        _Base::merge(__source._M_base());
>     >      }
>     >
>


[-- Attachment #2: merge.patch --]
[-- Type: text/x-patch, Size: 42635 bytes --]

diff --git a/libstdc++-v3/include/bits/hashtable.h b/libstdc++-v3/include/bits/hashtable.h
index ff8af2201cd..26f55cbc51b 100644
--- a/libstdc++-v3/include/bits/hashtable.h
+++ b/libstdc++-v3/include/bits/hashtable.h
@@ -1062,16 +1062,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       }
 
       /// Merge from a compatible container into one with unique keys.
-      template<typename _Compatible_Hashtable>
+      template<typename _Compatible_Hashtable, typename _OnExtract>
 	void
-	_M_merge_unique(_Compatible_Hashtable& __src) noexcept
+	_M_merge_unique(_Compatible_Hashtable& __src,
+			const _OnExtract& __on_extract)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  auto __n_elt = __src.size();
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
 	    {
 	      auto __pos = __i++;
 	      const key_type& __k = _ExtractKey{}(*__pos);
@@ -1080,6 +1081,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	      if (_M_find_node(__bkt, __k, __code) == nullptr)
 		{
 		  auto __nh = __src.extract(__pos);
+		  __on_extract(__pos);
 		  _M_insert_unique_node(__bkt, __code, __nh._M_ptr, __n_elt);
 		  __nh._M_ptr = nullptr;
 		  __n_elt = 1;
@@ -1090,17 +1092,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 
       /// Merge from a compatible container into one with equivalent keys.
-      template<typename _Compatible_Hashtable>
+      template<typename _Compatible_Hashtable, typename _OnExtract>
 	void
-	_M_merge_multi(_Compatible_Hashtable& __src) noexcept
+	_M_merge_multi(_Compatible_Hashtable& __src,
+		       const _OnExtract& __on_extract)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  this->reserve(size() + __src.size());
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
-	    _M_reinsert_node_multi(cend(), __src.extract(__i++));
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
+	    {
+	      auto __pos = __i++;
+	      auto __nh = __src.extract(__pos);
+	      __on_extract(__pos);
+	      _M_reinsert_node_multi(cend(), std::move(__nh));
+	    }
 	}
 #endif // C++17
 
diff --git a/libstdc++-v3/include/bits/hashtable_policy.h b/libstdc++-v3/include/bits/hashtable_policy.h
index 994c7b61046..23ebd137467 100644
--- a/libstdc++-v3/include/bits/hashtable_policy.h
+++ b/libstdc++-v3/include/bits/hashtable_policy.h
@@ -59,19 +59,19 @@ namespace __detail
 
   // Helper function: return distance(first, last) for forward
   // iterators, or 0/1 for input iterators.
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::input_iterator_tag)
     { return __first != __last ? 1 : 0; }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::forward_iterator_tag)
     { return std::distance(__first, __last); }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last)
     { return __distance_fw(__first, __last,
diff --git a/libstdc++-v3/include/bits/unordered_map.h b/libstdc++-v3/include/bits/unordered_map.h
index 2261e6685ea..e7e68c5e3eb 100644
--- a/libstdc++-v3/include/bits/unordered_map.h
+++ b/libstdc++-v3/include/bits/unordered_map.h
@@ -806,15 +806,39 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
       { _M_h.swap(__x._M_h); }
 
 #if __cplusplus > 201402L
+    protected:
       template<typename, typename, typename>
 	friend class std::_Hash_merge_helper;
 
+      template<typename _H2, typename _P2, typename _OnExtract>
+	void
+	_M_merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source,
+		 const _OnExtract& __on_extract)
+	{
+	  using _Merge_helper = _Hash_merge_helper<unordered_map, _H2, _P2>;
+	  _M_h._M_merge_unique(_Merge_helper::_S_get_table(__source),
+			       __on_extract);
+	}
+
+      template<typename _H2, typename _P2, typename _OnExtract>
+	void
+	_M_merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source,
+		 const _OnExtract& __on_extract)
+	{
+	  using _Merge_helper = _Hash_merge_helper<unordered_map, _H2, _P2>;
+	  _M_h._M_merge_unique(_Merge_helper::_S_get_table(__source),
+			       __on_extract);
+	}
+
+    public:
       template<typename _H2, typename _P2>
 	void
 	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
 	{
-	  using _Merge_helper = _Hash_merge_helper<unordered_map, _H2, _P2>;
-	  _M_h._M_merge_unique(_Merge_helper::_S_get_table(__source));
+	  using _Um_ite
+	    = typename unordered_map<_Key, _Tp,
+				     _H2, _P2, _Alloc>::const_iterator;
+	  _M_merge(__source, [](_Um_ite){ });
 	}
 
       template<typename _H2, typename _P2>
@@ -826,8 +850,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 	void
 	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
 	{
-	  using _Merge_helper = _Hash_merge_helper<unordered_map, _H2, _P2>;
-	  _M_h._M_merge_unique(_Merge_helper::_S_get_table(__source));
+	  using _Umm_ite =
+	    typename unordered_multimap<_Key, _Tp,
+					_H2, _P2, _Alloc>::const_iterator;
+	  _M_merge(__source, [](_Umm_ite){ });
 	}
 
       template<typename _H2, typename _P2>
@@ -1747,16 +1773,41 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
       { _M_h.swap(__x._M_h); }
 
 #if __cplusplus > 201402L
+    protected:
       template<typename, typename, typename>
 	friend class std::_Hash_merge_helper;
 
-      template<typename _H2, typename _P2>
+      template<typename _H2, typename _P2, typename _OnExtract>
 	void
-	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	_M_merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source,
+		 const _OnExtract& __on_extract)
 	{
 	  using _Merge_helper
 	    = _Hash_merge_helper<unordered_multimap, _H2, _P2>;
-	  _M_h._M_merge_multi(_Merge_helper::_S_get_table(__source));
+	  _M_h._M_merge_multi(_Merge_helper::_S_get_table(__source),
+			      __on_extract);
+	}
+
+      template<typename _H2, typename _P2, typename _OnExtract>
+	void
+	_M_merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source,
+		 const _OnExtract& __on_extract)
+	{
+	  using _Merge_helper
+	    = _Hash_merge_helper<unordered_multimap, _H2, _P2>;
+	  _M_h._M_merge_multi(_Merge_helper::_S_get_table(__source),
+			      __on_extract);
+	}
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  using _Umm_ite =
+	    typename unordered_multimap<_Key, _Tp,
+					_H2, _P2, _Alloc>::const_iterator;
+	  _M_merge(__source, [](_Umm_ite){ });
 	}
 
       template<typename _H2, typename _P2>
@@ -1768,9 +1819,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 	void
 	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
 	{
-	  using _Merge_helper
-	    = _Hash_merge_helper<unordered_multimap, _H2, _P2>;
-	  _M_h._M_merge_multi(_Merge_helper::_S_get_table(__source));
+	  using _Um_ite
+	    = typename unordered_map<_Key, _Tp,
+				     _H2, _P2, _Alloc>::const_iterator;
+	  _M_merge(__source, [](_Um_ite){ });
 	}
 
       template<typename _H2, typename _P2>
diff --git a/libstdc++-v3/include/bits/unordered_set.h b/libstdc++-v3/include/bits/unordered_set.h
index ac4a890d25a..4da83c69320 100644
--- a/libstdc++-v3/include/bits/unordered_set.h
+++ b/libstdc++-v3/include/bits/unordered_set.h
@@ -588,15 +588,39 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
       { _M_h.swap(__x._M_h); }
 
 #if __cplusplus > 201402L
+    protected:
       template<typename, typename, typename>
 	friend class std::_Hash_merge_helper;
 
+      template<typename _H2, typename _P2, typename _OnExtract>
+	void
+	_M_merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source,
+		 const _OnExtract& __on_extract)
+	{
+	  using _Merge_helper = _Hash_merge_helper<unordered_set, _H2, _P2>;
+	  _M_h._M_merge_unique(_Merge_helper::_S_get_table(__source),
+			       __on_extract);
+	}
+
+      template<typename _H2, typename _P2, typename _OnExtract>
+	void
+	_M_merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source,
+		 const _OnExtract& __on_extract)
+	{
+	  using _Merge_helper = _Hash_merge_helper<unordered_set, _H2, _P2>;
+	  _M_h._M_merge_unique(_Merge_helper::_S_get_table(__source),
+			       __on_extract);
+	}
+
+    public:
       template<typename _H2, typename _P2>
 	void
 	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
 	{
-	  using _Merge_helper = _Hash_merge_helper<unordered_set, _H2, _P2>;
-	  _M_h._M_merge_unique(_Merge_helper::_S_get_table(__source));
+	  using _Us_ite
+	    = typename unordered_set<_Value,
+				     _H2, _P2, _Alloc>::const_iterator;
+	  _M_merge(__source, [](_Us_ite){ });
 	}
 
       template<typename _H2, typename _P2>
@@ -608,8 +632,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 	void
 	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
 	{
-	  using _Merge_helper = _Hash_merge_helper<unordered_set, _H2, _P2>;
-	  _M_h._M_merge_unique(_Merge_helper::_S_get_table(__source));
+	  using _Ums_ite
+	    = typename unordered_multiset<_Value,
+					  _H2, _P2, _Alloc>::const_iterator;
+	  _M_merge(__source, [](_Ums_ite){ });
 	}
 
       template<typename _H2, typename _P2>
@@ -1436,16 +1462,41 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
       { _M_h.swap(__x._M_h); }
 
 #if __cplusplus > 201402L
+    protected:
       template<typename, typename, typename>
 	friend class std::_Hash_merge_helper;
 
-      template<typename _H2, typename _P2>
+      template<typename _H2, typename _P2, typename _OnExtract>
 	void
-	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	_M_merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source,
+		 const _OnExtract& __on_extract)
 	{
 	  using _Merge_helper
 	    = _Hash_merge_helper<unordered_multiset, _H2, _P2>;
-	  _M_h._M_merge_multi(_Merge_helper::_S_get_table(__source));
+	  _M_h._M_merge_multi(_Merge_helper::_S_get_table(__source),
+			      __on_extract);
+	}
+
+      template<typename _H2, typename _P2, typename _OnExtract>
+	void
+	_M_merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source,
+		 const _OnExtract& __on_extract)
+	{
+	  using _Merge_helper
+	    = _Hash_merge_helper<unordered_multiset, _H2, _P2>;
+	  _M_h._M_merge_multi(_Merge_helper::_S_get_table(__source),
+			      __on_extract);
+	}
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  using _UMs_ite
+	    = typename unordered_multiset<_Value,
+					  _H2, _P2, _Alloc>::const_iterator;
+	  _M_merge(__source, [](_UMs_ite){ });
 	}
 
       template<typename _H2, typename _P2>
@@ -1457,9 +1508,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
 	void
 	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
 	{
-	  using _Merge_helper
-	    = _Hash_merge_helper<unordered_multiset, _H2, _P2>;
-	  _M_h._M_merge_multi(_Merge_helper::_S_get_table(__source));
+	  using _Us_ite
+	    = typename unordered_set<_Value,
+				     _H2, _P2, _Alloc>::const_iterator;
+	  _M_merge(__source, [](_Us_ite){ });
 	}
 
       template<typename _H2, typename _P2>
diff --git a/libstdc++-v3/include/debug/safe_container.h b/libstdc++-v3/include/debug/safe_container.h
index 97c47167fe8..5de55d69f34 100644
--- a/libstdc++-v3/include/debug/safe_container.h
+++ b/libstdc++-v3/include/debug/safe_container.h
@@ -78,7 +78,6 @@ namespace __gnu_debug
       { }
 #endif
 
-    public:
       // Copy assignment invalidate all iterators.
       _Safe_container&
       operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT
diff --git a/libstdc++-v3/include/debug/safe_unordered_container.h b/libstdc++-v3/include/debug/safe_unordered_container.h
index aae1e2dab60..06d0e91282c 100644
--- a/libstdc++-v3/include/debug/safe_unordered_container.h
+++ b/libstdc++-v3/include/debug/safe_unordered_container.h
@@ -72,6 +72,7 @@ namespace __gnu_debug
 		{ return __it != __local_end; });
       }
 
+    public:
       void
       _M_invalidate_all()
       {
diff --git a/libstdc++-v3/include/debug/unordered_map b/libstdc++-v3/include/debug/unordered_map
index bb697d364ea..a00eb8fe2e0 100644
--- a/libstdc++-v3/include/debug/unordered_map
+++ b/libstdc++-v3/include/debug/unordered_map
@@ -97,7 +97,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_map>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -106,6 +111,7 @@ namespace __debug
 	_Base_local_iterator, unordered_map>		local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_map>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_map() = default;
 
@@ -209,6 +215,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_map& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -291,6 +302,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -298,6 +313,8 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -538,9 +555,52 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  using __base_it_t = decltype(__source._M_base().cbegin());
+	  auto __on_extract =
+	    [&__source](__base_it_t __extracted_it)
+	    {
+	      auto __pred = [__extracted_it](auto __it)
+			    { return __it == __extracted_it; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    };
+	  this->_M_merge(__source._M_base(), __on_extract);
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  using __base_it_t = decltype(__source._M_base().cbegin());
+	  auto __on_extract =
+	    [&__source](__base_it_t __extracted_it)
+	    {
+	      auto __pred = [__extracted_it](auto __it)
+			    { return __it == __extracted_it; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    };
+	  this->_M_merge(__source._M_base(), __on_extract);
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -567,6 +627,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -605,6 +670,9 @@ namespace __debug
 	}
 #endif
 
+      using _Base::operator[];
+      using _Base::at;
+
       size_type
       erase(const key_type& __key)
       {
@@ -651,6 +719,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept	{ return *this; }
 
@@ -843,7 +914,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multimap>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -852,6 +928,7 @@ namespace __debug
 	_Base_local_iterator, unordered_multimap>	local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_multimap>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_multimap() = default;
 
@@ -952,6 +1029,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multimap& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -1034,6 +1116,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -1192,9 +1278,52 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  using __base_it_t = decltype(__source._M_base().cbegin());
+	  auto __on_extract =
+	    [&__source](__base_it_t __extracted_it)
+	    {
+	      auto __pred = [__extracted_it](auto __it)
+			    { return __it == __extracted_it; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    };
+	  this->_M_merge(__source._M_base(), __on_extract);
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  using __base_it_t = decltype(__source._M_base().cbegin());
+	  auto __on_extract =
+	    [&__source](__base_it_t __extracted_it)
+	    {
+	      auto __pred = [__extracted_it](auto __it)
+			    { return __it == __extracted_it; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    };
+	  this->_M_merge(__source._M_base(), __on_extract);
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1221,6 +1350,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -1309,6 +1443,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept { return *this; }
 
diff --git a/libstdc++-v3/include/debug/unordered_set b/libstdc++-v3/include/debug/unordered_set
index c25910946b7..3df02fcdef0 100644
--- a/libstdc++-v3/include/debug/unordered_set
+++ b/libstdc++-v3/include/debug/unordered_set
@@ -88,6 +88,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -95,6 +96,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_set>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -203,6 +208,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_set& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -285,6 +295,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -292,6 +305,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -303,6 +319,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	std::pair<iterator, bool>
 	emplace(_Args&&... __args)
@@ -423,9 +442,52 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  using __base_it_t = decltype(__source._M_base().cbegin());
+	  auto __on_extract =
+	    [&__source](__base_it_t __extracted_it)
+	    {
+	      auto __pred = [__extracted_it](auto __it)
+			    { return __it == __extracted_it; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    };
+	  this->_M_merge(__source._M_base(), __on_extract);
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  using __base_it_t = decltype(__source._M_base().cbegin());
+	  auto __on_extract =
+	    [&__source](__base_it_t __extracted_it)
+	    {
+	      auto __pred = [__extracted_it](auto __it)
+			    { return __it == __extracted_it; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    };
+	  this->_M_merge(__source._M_base(), __on_extract);
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -452,6 +514,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -707,6 +775,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -714,6 +783,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multiset>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -822,6 +895,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multiset& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -904,6 +982,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -911,6 +992,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -922,6 +1006,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	iterator
 	emplace(_Args&&... __args)
@@ -1037,9 +1124,52 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  using __base_it_t = decltype(__source._M_base().cbegin());
+	  auto __on_extract =
+	    [&__source](__base_it_t __extracted_it)
+	    {
+	      auto __pred = [__extracted_it](auto __it)
+			    { return __it == __extracted_it; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    };
+	  this->_M_merge(__source._M_base(), __on_extract);
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  using __base_it_t = decltype(__source._M_base().cbegin());
+	  auto __on_extract =
+	    [&__source](__base_it_t __extracted_it)
+	    {
+	      auto __pred = [__extracted_it](auto __it)
+			    { return __it == __extracted_it; };
+	      __source._M_invalidate_if(__pred);
+	      __source._M_invalidate_local_if(__pred);
+	    };
+	  this->_M_merge(__source._M_base(), __on_extract);
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1066,6 +1196,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
new file mode 100644
index 00000000000..69e8a6741a8
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it4 != it2 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
new file mode 100644
index 00000000000..543cd960a5e
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
new file mode 100644
index 00000000000..8e234799cbf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
new file mode 100644
index 00000000000..3c9c8268f8c
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
new file mode 100644
index 00000000000..25b3b9e0c75
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
new file mode 100644
index 00000000000..8d28d83b972
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
new file mode 100644
index 00000000000..5db91a27ca0
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
new file mode 100644
index 00000000000..a1636703569
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
new file mode 100644
index 00000000000..bce8da7f6cf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
new file mode 100644
index 00000000000..72317a32e89
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
new file mode 100644
index 00000000000..1b1f4870dd1
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
new file mode 100644
index 00000000000..5005cf8468a
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
new file mode 100644
index 00000000000..8a2bc6e468f
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
new file mode 100644
index 00000000000..3ac96540770
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
new file mode 100644
index 00000000000..7e93b55d507
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
new file mode 100644
index 00000000000..14c8ff63b05
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.h b/libstdc++-v3/testsuite/util/testsuite_abi.h
index 667c46c33d3..4a0cf64f6fb 100644
--- a/libstdc++-v3/testsuite/util/testsuite_abi.h
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
@@ -24,7 +24,11 @@
 #include <locale>
 #if __cplusplus >= 201103L
 # include <unordered_map>
+# ifdef _GLIBCXX_DEBUG
+namespace unord = std::_GLIBCXX_STD_C;
+# else
 namespace unord = std;
+# endif
 #else
 # include <tr1/unordered_map>
 namespace unord = std::tr1;

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-10-21 16:51       ` François Dumont
@ 2021-10-21 16:55         ` Jonathan Wakely
  2021-10-22  5:22           ` François Dumont
  0 siblings, 1 reply; 21+ messages in thread
From: Jonathan Wakely @ 2021-10-21 16:55 UTC (permalink / raw)
  To: François Dumont; +Cc: Jonathan Wakely, libstdc++, gcc-patches

On Thu, 21 Oct 2021 at 17:52, François Dumont <frs.dumont@gmail.com> wrote:

> I eventually would like to propose a different approach.
>
> I am adding a hook in normal implementation to let the _GLIBCXX_DEBUG code
> know when a node is being extracted. This way invalidation is only done by
> comparing nodes, no need to compute hash code for this operation.
>

Ugh, this is horrible, I don't like the normal mode depending on the debug
mode (even if it's just having to add hooks like this).

The previous patch seemed fine to me. Already an improvement on what is on
trunk now.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-10-21 16:55         ` Jonathan Wakely
@ 2021-10-22  5:22           ` François Dumont
  0 siblings, 0 replies; 21+ messages in thread
From: François Dumont @ 2021-10-22  5:22 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Jonathan Wakely, libstdc++, gcc-patches

On 21/10/21 6:55 pm, Jonathan Wakely wrote:
>
>
> On Thu, 21 Oct 2021 at 17:52, François Dumont <frs.dumont@gmail.com 
> <mailto:frs.dumont@gmail.com>> wrote:
>
>     I eventually would like to propose a different approach.
>
>     I am adding a hook in normal implementation to let the
>     _GLIBCXX_DEBUG code know when a node is being extracted. This way
>     invalidation is only done by comparing nodes, no need to compute
>     hash code for this operation.
>
>
> Ugh, this is horrible, I don't like the normal mode depending on the 
> debug mode (even if it's just having to add hooks like this).

Yes, I was relunctant to do so but in this case I was not able to find 
another way to provide the same result as here.

Ok, I'll come back to the other patch and just invalidate all iterators 
in case of exception.

>
> The previous patch seemed fine to me. Already an improvement on what 
> is on trunk now.
>


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-10-16 14:52     ` Jonathan Wakely
  2021-10-21 16:51       ` François Dumont
@ 2021-10-25 18:08       ` François Dumont
  2021-11-06 13:51         ` François Dumont
  1 sibling, 1 reply; 21+ messages in thread
From: François Dumont @ 2021-10-25 18:08 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Jonathan Wakely, libstdc++, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 9019 bytes --]

New patch with the proposed workaround below.

I also slightly change the _M_merge_multi implementation so that if the 
new hash code computation raise an exception the node is simply not 
extracted rather than extracted and then released. This way, if it takes 
place on the 1st moved node the _GLIBCXX_DEBUG mode won't try to 
invalidate anything because the source size won't have changed.

Ok to commit ?

François


On 16/10/21 4:52 pm, Jonathan Wakely wrote:
>
>
> On Sat, 16 Oct 2021, 14:49 François Dumont via Libstdc++, 
> <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>
>     Hi
>
>          Here is the new proposal. My only concern is that we are also
>     using
>     hash or equal_to functors in the guard destructor.
>
>
>
> Can we catch any exception there, invalidate all iterators, and not 
> rethrow the exception?
>
>
>          I am going to enhance merge normal implementation to make use of
>     the cached hash code when hash functors are the same between the
>     source
>     and destination of nodes. Maybe I'll be able to make use of it in
>     Debug
>     implementation too.
>
>     François
>
>
>     On 14/10/21 10:23 am, Jonathan Wakely wrote:
>     > On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
>     > <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>     >> Hi
>     >>
>     >>       libstdc++: [_GLIBCXX_DEBUG] Implement unordered container
>     merge
>     >>
>     >>       The _GLIBCXX_DEBUG unordered containers need a dedicated
>     merge
>     >> implementation
>     >>       so that any existing iterator on the transfered nodes is
>     properly
>     >> invalidated.
>     >>
>     >>       Add typedef/using declaration for everything used as-is
>     from normal
>     >> implementation.
>     >>
>     >>       libstdc++-v3/ChangeLog:
>     >>
>     >>               * include/debug/safe_container.h
>     (_Safe_container<>): Make
>     >> all methods
>     >>               protected.
>     >>               * include/debug/safe_unordered_container.h
>     >>  (_Safe_unordered_container<>::_M_invalide_all): Make public.
>     >>  (_Safe_unordered_container<>::_M_invalide_if): Likewise.
>     >> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>     >>               * include/debug/unordered_map
>     >>  (unordered_map<>::mapped_type, pointer, const_pointer): New
>     >> typedef.
>     >>               (unordered_map<>::reference, const_reference,
>     >> difference_type): New typedef.
>     >>  (unordered_map<>::get_allocator, empty, size, max_size):
>     >> Add usings.
>     >>  (unordered_map<>::bucket_count, max_bucket_count, bucket):
>     >> Add usings.
>     >>  (unordered_map<>::hash_function, key_equal, count,
>     >> contains): Add usings.
>     >>               (unordered_map<>::operator[], at, rehash,
>     reserve): Add usings.
>     >>               (unordered_map<>::merge): New.
>     >>  (unordered_multimap<>::mapped_type, pointer,
>     >> const_pointer): New typedef.
>     >>  (unordered_multimap<>::reference, const_reference,
>     >> difference_type): New typedef.
>     >>  (unordered_multimap<>::get_allocator, empty, size,
>     >> max_size): Add usings.
>     >>  (unordered_multimap<>::bucket_count, max_bucket_count,
>     >> bucket): Add usings.
>     >>  (unordered_multimap<>::hash_function, key_equal, count,
>     >> contains): Add usings.
>     >>  (unordered_multimap<>::rehash, reserve): Add usings.
>     >>  (unordered_multimap<>::merge): New.
>     >>               * include/debug/unordered_set
>     >>  (unordered_set<>::mapped_type, pointer, const_pointer): New
>     >> typedef.
>     >>               (unordered_set<>::reference, const_reference,
>     >> difference_type): New typedef.
>     >>  (unordered_set<>::get_allocator, empty, size, max_size):
>     >> Add usings.
>     >>  (unordered_set<>::bucket_count, max_bucket_count, bucket):
>     >> Add usings.
>     >>  (unordered_set<>::hash_function, key_equal, count,
>     >> contains): Add usings.
>     >>               (unordered_set<>::rehash, reserve): Add usings.
>     >>               (unordered_set<>::merge): New.
>     >>  (unordered_multiset<>::mapped_type, pointer,
>     >> const_pointer): New typedef.
>     >>  (unordered_multiset<>::reference, const_reference,
>     >> difference_type): New typedef.
>     >>  (unordered_multiset<>::get_allocator, empty, size,
>     >> max_size): Add usings.
>     >>  (unordered_multiset<>::bucket_count, max_bucket_count,
>     >> bucket): Add usings.
>     >>  (unordered_multiset<>::hash_function, key_equal, count,
>     >> contains): Add usings.
>     >>  (unordered_multiset<>::rehash, reserve): Add usings.
>     >>  (unordered_multiset<>::merge): New.
>     >>               *
>     >> testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc:
>     New test.
>     >>               *
>     >> testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New
>     test.
>     >>               *
>     >> testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New
>     test.
>     >>               * testsuite/util/testsuite_abi.h:
>     [_GLIBCXX_DEBUG] Use
>     >> normal unordered container implementation.
>     >>
>     >> Tested under Linux x86_64.
>     >>
>     >> Ok to commit ?
>     > Yes, thanks. But ...
>     >
>     > This looks like an improvement over what we have now, but not 100%
>     > correct. The merge functions can exit via exception (if any hash
>     > function or equality predicate throws), and if that happens the safe
>     > iterators will not get invalidated. I think we need to call
>     > _Base::merge in a try-block, and do the iterator invalidation
>     whether
>     > we return normally or via an exception.
>     >
>     > Something like:
>     >
>     >    template<typename _H2, typename _P2>
>     >      void
>     >      merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
>     >      {
>     >        struct _Guard
>     >        {
>     >          _Guard(unordered_set& __source) noexcept
>     >          : _M_source(__source), _M_size(__source.size())
>     >          { }
>     >
>     >          ~_Guard()
>     >          {
>     >            const size_type __size = _M_source.size();
>     >            if (__size != _M_size)
>     >              {
>     >                if (__size == 0)
>     >                  _M_source._M_invalidate_all();
>     >                else
>     >                  {
>     >                    auto __pred = [&__source](auto __it)
>     >                                  { return __source.count(*__it)
>     == 0; };
>     >                    __source._M_invalidate_if(__pred);
>     > __source._M_invalidate_local_if(__pred);
>     >                  }
>     >              }
>     >          }
>     >
>     >          _Guard(const _Guard&) = delete;
>     >
>     >          unordered_set& _M_source;
>     >          const size_type _M_size;
>     >        };
>     >        _Guard __guard(__source);
>     >        _Base::merge(__source._M_base());
>     >      }
>     >
>


[-- Attachment #2: merge.patch --]
[-- Type: text/x-patch, Size: 39019 bytes --]

diff --git a/libstdc++-v3/include/bits/hashtable.h b/libstdc++-v3/include/bits/hashtable.h
index ff8af2201cd..dc011013d95 100644
--- a/libstdc++-v3/include/bits/hashtable.h
+++ b/libstdc++-v3/include/bits/hashtable.h
@@ -1064,14 +1064,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Merge from a compatible container into one with unique keys.
       template<typename _Compatible_Hashtable>
 	void
-	_M_merge_unique(_Compatible_Hashtable& __src) noexcept
+	_M_merge_unique(_Compatible_Hashtable& __src)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  auto __n_elt = __src.size();
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
 	    {
 	      auto __pos = __i++;
 	      const key_type& __k = _ExtractKey{}(*__pos);
@@ -1092,15 +1092,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Merge from a compatible container into one with equivalent keys.
       template<typename _Compatible_Hashtable>
 	void
-	_M_merge_multi(_Compatible_Hashtable& __src) noexcept
+	_M_merge_multi(_Compatible_Hashtable& __src)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  this->reserve(size() + __src.size());
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
-	    _M_reinsert_node_multi(cend(), __src.extract(__i++));
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
+	    {
+	      auto __pos = __i++;
+	      const key_type& __k = _ExtractKey{}(*__pos);
+	      __hash_code __code = this->_M_hash_code(__k);
+	      auto __nh = __src.extract(__pos);
+	      _M_insert_multi_node(nullptr, __code, __nh._M_ptr);
+	      __nh._M_ptr = nullptr;
+	    }
 	}
 #endif // C++17
 
diff --git a/libstdc++-v3/include/bits/hashtable_policy.h b/libstdc++-v3/include/bits/hashtable_policy.h
index 994c7b61046..23ebd137467 100644
--- a/libstdc++-v3/include/bits/hashtable_policy.h
+++ b/libstdc++-v3/include/bits/hashtable_policy.h
@@ -59,19 +59,19 @@ namespace __detail
 
   // Helper function: return distance(first, last) for forward
   // iterators, or 0/1 for input iterators.
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::input_iterator_tag)
     { return __first != __last ? 1 : 0; }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::forward_iterator_tag)
     { return std::distance(__first, __last); }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last)
     { return __distance_fw(__first, __last,
diff --git a/libstdc++-v3/include/debug/safe_container.h b/libstdc++-v3/include/debug/safe_container.h
index 97c47167fe8..5de55d69f34 100644
--- a/libstdc++-v3/include/debug/safe_container.h
+++ b/libstdc++-v3/include/debug/safe_container.h
@@ -78,7 +78,6 @@ namespace __gnu_debug
       { }
 #endif
 
-    public:
       // Copy assignment invalidate all iterators.
       _Safe_container&
       operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT
diff --git a/libstdc++-v3/include/debug/safe_unordered_container.h b/libstdc++-v3/include/debug/safe_unordered_container.h
index aae1e2dab60..06d0e91282c 100644
--- a/libstdc++-v3/include/debug/safe_unordered_container.h
+++ b/libstdc++-v3/include/debug/safe_unordered_container.h
@@ -72,6 +72,7 @@ namespace __gnu_debug
 		{ return __it != __local_end; });
       }
 
+    public:
       void
       _M_invalidate_all()
       {
diff --git a/libstdc++-v3/include/debug/unordered_map b/libstdc++-v3/include/debug/unordered_map
index bb697d364ea..927361cbc48 100644
--- a/libstdc++-v3/include/debug/unordered_map
+++ b/libstdc++-v3/include/debug/unordered_map
@@ -97,7 +97,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_map>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -106,6 +111,7 @@ namespace __debug
 	_Base_local_iterator, unordered_map>		local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_map>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_map() = default;
 
@@ -209,6 +215,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_map& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -291,6 +302,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -298,6 +313,8 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -538,9 +555,127 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+    private:
+      template<typename _Source>
+	struct _UMapGuard
+	{
+	  _UMapGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMapGuard(const _UMapGuard&) = delete;
+
+	  ~_UMapGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      { return _M_source.count(__it->second) == 0; };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+	};
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _UMapGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+    private:
+      template<typename _Source>
+	struct _UMMapGuard
+	{
+	  _UMMapGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMMapGuard(const _UMMapGuard&) = delete;
+
+	  ~_UMMapGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      {
+			auto __rng =
+			  _M_source._M_base().equal_range(__it->first);
+			for (auto __rit = __rng.first;
+			     __rit != __rng.second; ++__rit)
+			  {
+			    if (__it == __rit)
+			      return false;
+			  }
+
+			return true;
+		      };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+        };
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _UMMapGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -567,6 +702,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -605,6 +745,9 @@ namespace __debug
 	}
 #endif
 
+      using _Base::operator[];
+      using _Base::at;
+
       size_type
       erase(const key_type& __key)
       {
@@ -651,6 +794,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept	{ return *this; }
 
@@ -843,7 +989,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multimap>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -852,6 +1003,7 @@ namespace __debug
 	_Base_local_iterator, unordered_multimap>	local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_multimap>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_multimap() = default;
 
@@ -952,6 +1104,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multimap& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -1034,6 +1191,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -1192,9 +1353,130 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+    private:
+      template<typename _Source>
+	struct _UMMapGuard
+	{
+	  _UMMapGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMMapGuard(const _UMMapGuard&) = delete;
+
+	  ~_UMMapGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    // An exception was raised, find out which iterator to
+		    // invalidate.
+		    auto __pred =
+		      [this](auto __it)
+		      {
+			auto __rng =
+			  _M_source._M_base().equal_range(__it->first);
+			for (auto __rit = __rng.first;
+			     __rit != __rng.second; ++__rit)
+			  {
+			    if (__it == __rit)
+			      return false;
+			  }
+
+			return true;
+		      };
+
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+        };
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _UMMapGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+    private:
+      template<typename _Source>
+	struct _UMapGuard
+	{
+	  _UMapGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMapGuard(const _UMapGuard&) = delete;
+
+	  ~_UMapGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      { return _M_source.count(__it->second) == 0; };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+	};
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  _UMapGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1221,6 +1503,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -1309,6 +1596,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept { return *this; }
 
diff --git a/libstdc++-v3/include/debug/unordered_set b/libstdc++-v3/include/debug/unordered_set
index c25910946b7..6bc4b050439 100644
--- a/libstdc++-v3/include/debug/unordered_set
+++ b/libstdc++-v3/include/debug/unordered_set
@@ -88,6 +88,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -95,6 +96,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_set>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -203,6 +208,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_set& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -285,6 +295,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -292,6 +305,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -303,6 +319,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	std::pair<iterator, bool>
 	emplace(_Args&&... __args)
@@ -423,9 +442,125 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+    private:
+      template<typename _Source>
+	struct _USetGuard
+	{
+	  _USetGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _USetGuard(const _USetGuard&) = delete;
+
+	  ~_USetGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred = [this](auto __it)
+				  { return _M_source.count(*__it) == 0; };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+	};
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _USetGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+    private:
+      template<typename _Source>
+	struct _UMSetGuard
+	{
+	  _UMSetGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMSetGuard(const _UMSetGuard&) = delete;
+
+	  ~_UMSetGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      {
+			auto __rng = _M_source._M_base().equal_range(*__it);
+			for (auto __rit = __rng.first;
+			     __rit != __rng.second; ++__rit)
+			  {
+			    if (__it == __rit)
+			      return false;
+			  }
+
+			return true;
+		      };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+	};
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _UMSetGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -452,6 +587,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -707,6 +848,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -714,6 +856,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multiset>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -822,6 +968,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multiset& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -904,6 +1055,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -911,6 +1065,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -922,6 +1079,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	iterator
 	emplace(_Args&&... __args)
@@ -1037,9 +1197,125 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+    private:
+      template<typename _Source>
+	struct _UMSetGuard
+	{
+	  _UMSetGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMSetGuard(const _UMSetGuard&) = delete;
+
+	  ~_UMSetGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      {
+			auto __rng = _M_source._M_base().equal_range(*__it);
+			for (auto __rit = __rng.first;
+			     __rit != __rng.second; ++__rit)
+			  {
+			    if (__it == __rit)
+			      return false;
+			  }
+
+			return true;
+		      };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+	};
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _UMSetGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+    private:
+      template<typename _Source>
+	struct _USetGuard
+	{
+	  _USetGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _USetGuard(const _USetGuard&) = delete;
+
+	  ~_USetGuard()
+	  {
+	    const size_type __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred = [this](auto __it)
+				  { return _M_source.count(*__it) == 0; };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const size_type _M_size;
+	};
+
+    public:
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  _USetGuard __guard(__source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1066,6 +1342,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
new file mode 100644
index 00000000000..69e8a6741a8
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it4 != it2 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
new file mode 100644
index 00000000000..543cd960a5e
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
new file mode 100644
index 00000000000..8e234799cbf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
new file mode 100644
index 00000000000..3c9c8268f8c
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
new file mode 100644
index 00000000000..25b3b9e0c75
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
new file mode 100644
index 00000000000..8d28d83b972
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
new file mode 100644
index 00000000000..5db91a27ca0
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
new file mode 100644
index 00000000000..a1636703569
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
new file mode 100644
index 00000000000..bce8da7f6cf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
new file mode 100644
index 00000000000..72317a32e89
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
new file mode 100644
index 00000000000..1b1f4870dd1
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
new file mode 100644
index 00000000000..5005cf8468a
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
new file mode 100644
index 00000000000..8a2bc6e468f
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
new file mode 100644
index 00000000000..3ac96540770
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
new file mode 100644
index 00000000000..7e93b55d507
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
new file mode 100644
index 00000000000..14c8ff63b05
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.h b/libstdc++-v3/testsuite/util/testsuite_abi.h
index 667c46c33d3..4a0cf64f6fb 100644
--- a/libstdc++-v3/testsuite/util/testsuite_abi.h
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
@@ -24,7 +24,11 @@
 #include <locale>
 #if __cplusplus >= 201103L
 # include <unordered_map>
+# ifdef _GLIBCXX_DEBUG
+namespace unord = std::_GLIBCXX_STD_C;
+# else
 namespace unord = std;
+# endif
 #else
 # include <tr1/unordered_map>
 namespace unord = std::tr1;

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-10-25 18:08       ` François Dumont
@ 2021-11-06 13:51         ` François Dumont
  2021-11-08 21:36           ` François Dumont
  0 siblings, 1 reply; 21+ messages in thread
From: François Dumont @ 2021-11-06 13:51 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Jonathan Wakely, libstdc++, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 15559 bytes --]

You were right to delay your reply. Here is a new version with less code 
duplication and a bug fix in the new _UContMergeGuard where we were 
using it->second rather than it->first to get the key.

Note also that the change to _M_merge_multi implementation is also 
important because otherwise we might be trying to extract a key from a 
destructed node.

     libstdc++: [_GLIBCXX_DEBUG] Implement unordered container merge

     The _GLIBCXX_DEBUG unordered containers need a dedicated merge 
implementation
     so that any existing iterator on the transfered nodes is properly 
invalidated.

     Add typedef/using declaration for everything used as-is from normal 
implementation.

     libstdc++-v3/ChangeLog:

             * include/bits/hashtable_policy.h (__distance_fw): Replace 
class keyword with
             typename.
             * include/bits/hashtable.h (_Hashtable<>::_M_merge_unique): 
Remove noexcept
             qualification. Use const_iterator for node extraction/reinsert.
             (_Hashtable<>::_M_merge_multi): Likewise. Compute new hash 
code before extract.
             * include/debug/safe_container.h (_Safe_container<>): Make 
all methods
             protected.
             * include/debug/safe_unordered_container.h
(_Safe_unordered_container<>::_UContMergeGuard<_ExtractKey, _Source>): New.
(_Safe_unordered_container<>::_S_uc_guard<_ExtractKey, _Source>): New.
(_Safe_unordered_container<>::_UMContMergeGuard<_ExtractKey, _Source>): New.
(_Safe_unordered_container<>::_S_umc_guard<_ExtractKey, _Source>): New.
             (_Safe_unordered_container<>::_M_invalide_all): Make public.
             (_Safe_unordered_container<>::_M_invalide_if): Likewise.
(_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
             * include/debug/unordered_map
             (unordered_map<>::mapped_type, pointer, const_pointer): New 
typedef.
             (unordered_map<>::reference, const_reference, 
difference_type): New typedef.
             (unordered_map<>::get_allocator, empty, size, max_size): 
Add usings.
             (unordered_map<>::bucket_count, max_bucket_count, bucket): 
Add usings.
             (unordered_map<>::hash_function, key_equal, count, 
contains): Add usings.
             (unordered_map<>::operator[], at, rehash, reserve): Add usings.
             (unordered_map<>::merge): New.
             (unordered_multimap<>::mapped_type, pointer, 
const_pointer): New typedef.
             (unordered_multimap<>::reference, const_reference, 
difference_type): New typedef.
             (unordered_multimap<>::get_allocator, empty, size, 
max_size): Add usings.
             (unordered_multimap<>::bucket_count, max_bucket_count, 
bucket): Add usings.
             (unordered_multimap<>::hash_function, key_equal, count, 
contains): Add usings.
             (unordered_multimap<>::rehash, reserve): Add usings.
             (unordered_multimap<>::merge): New.
             * include/debug/unordered_set
             (unordered_set<>::mapped_type, pointer, const_pointer): New 
typedef.
             (unordered_set<>::reference, const_reference, 
difference_type): New typedef.
             (unordered_set<>::get_allocator, empty, size, max_size): 
Add usings.
             (unordered_set<>::bucket_count, max_bucket_count, bucket): 
Add usings.
             (unordered_set<>::hash_function, key_equal, count, 
contains): Add usings.
             (unordered_set<>::rehash, reserve): Add usings.
             (unordered_set<>::merge): New.
             (unordered_multiset<>::mapped_type, pointer, 
const_pointer): New typedef.
             (unordered_multiset<>::reference, const_reference, 
difference_type): New typedef.
             (unordered_multiset<>::get_allocator, empty, size, 
max_size): Add usings.
             (unordered_multiset<>::bucket_count, max_bucket_count, 
bucket): Add usings.
             (unordered_multiset<>::hash_function, key_equal, count, 
contains): Add usings.
             (unordered_multiset<>::rehash, reserve): Add usings.
             (unordered_multiset<>::merge): New.
             * 
testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New test.
             * 
testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New test.
             * 
testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New test.
             * 
testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc: New test.
             * 
testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc: New test.
             * 
testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New test.
             * 
testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New test.
             * 
testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New test.
             * 
testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New test.
             * testsuite/util/testsuite_abi.h: [_GLIBCXX_DEBUG] Use 
normal unordered container implementation.

Tested under Linux x86_64.

Ok to commit ?

François

On 25/10/21 8:08 pm, François Dumont wrote:
> New patch with the proposed workaround below.
>
> I also slightly change the _M_merge_multi implementation so that if 
> the new hash code computation raise an exception the node is simply 
> not extracted rather than extracted and then released. This way, if it 
> takes place on the 1st moved node the _GLIBCXX_DEBUG mode won't try to 
> invalidate anything because the source size won't have changed.
>
> Ok to commit ?
>
> François
>
>
> On 16/10/21 4:52 pm, Jonathan Wakely wrote:
>>
>>
>> On Sat, 16 Oct 2021, 14:49 François Dumont via Libstdc++, 
>> <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>>
>>     Hi
>>
>>          Here is the new proposal. My only concern is that we are
>>     also using
>>     hash or equal_to functors in the guard destructor.
>>
>>
>>
>> Can we catch any exception there, invalidate all iterators, and not 
>> rethrow the exception?
>>
>>
>>          I am going to enhance merge normal implementation to make
>>     use of
>>     the cached hash code when hash functors are the same between the
>>     source
>>     and destination of nodes. Maybe I'll be able to make use of it in
>>     Debug
>>     implementation too.
>>
>>     François
>>
>>
>>     On 14/10/21 10:23 am, Jonathan Wakely wrote:
>>     > On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
>>     > <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>>     >> Hi
>>     >>
>>     >>       libstdc++: [_GLIBCXX_DEBUG] Implement unordered
>>     container merge
>>     >>
>>     >>       The _GLIBCXX_DEBUG unordered containers need a dedicated
>>     merge
>>     >> implementation
>>     >>       so that any existing iterator on the transfered nodes is
>>     properly
>>     >> invalidated.
>>     >>
>>     >>       Add typedef/using declaration for everything used as-is
>>     from normal
>>     >> implementation.
>>     >>
>>     >>       libstdc++-v3/ChangeLog:
>>     >>
>>     >>               * include/debug/safe_container.h
>>     (_Safe_container<>): Make
>>     >> all methods
>>     >>               protected.
>>     >>               * include/debug/safe_unordered_container.h
>>     >>  (_Safe_unordered_container<>::_M_invalide_all): Make public.
>>     >>  (_Safe_unordered_container<>::_M_invalide_if): Likewise.
>>     >> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>>     >>               * include/debug/unordered_map
>>     >>  (unordered_map<>::mapped_type, pointer, const_pointer): New
>>     >> typedef.
>>     >>  (unordered_map<>::reference, const_reference,
>>     >> difference_type): New typedef.
>>     >>  (unordered_map<>::get_allocator, empty, size, max_size):
>>     >> Add usings.
>>     >>  (unordered_map<>::bucket_count, max_bucket_count, bucket):
>>     >> Add usings.
>>     >>  (unordered_map<>::hash_function, key_equal, count,
>>     >> contains): Add usings.
>>     >>  (unordered_map<>::operator[], at, rehash, reserve): Add usings.
>>     >>               (unordered_map<>::merge): New.
>>     >>  (unordered_multimap<>::mapped_type, pointer,
>>     >> const_pointer): New typedef.
>>     >>  (unordered_multimap<>::reference, const_reference,
>>     >> difference_type): New typedef.
>>     >>  (unordered_multimap<>::get_allocator, empty, size,
>>     >> max_size): Add usings.
>>     >>  (unordered_multimap<>::bucket_count, max_bucket_count,
>>     >> bucket): Add usings.
>>     >>  (unordered_multimap<>::hash_function, key_equal, count,
>>     >> contains): Add usings.
>>     >>  (unordered_multimap<>::rehash, reserve): Add usings.
>>     >>  (unordered_multimap<>::merge): New.
>>     >>               * include/debug/unordered_set
>>     >>  (unordered_set<>::mapped_type, pointer, const_pointer): New
>>     >> typedef.
>>     >>  (unordered_set<>::reference, const_reference,
>>     >> difference_type): New typedef.
>>     >>  (unordered_set<>::get_allocator, empty, size, max_size):
>>     >> Add usings.
>>     >>  (unordered_set<>::bucket_count, max_bucket_count, bucket):
>>     >> Add usings.
>>     >>  (unordered_set<>::hash_function, key_equal, count,
>>     >> contains): Add usings.
>>     >>               (unordered_set<>::rehash, reserve): Add usings.
>>     >>               (unordered_set<>::merge): New.
>>     >>  (unordered_multiset<>::mapped_type, pointer,
>>     >> const_pointer): New typedef.
>>     >>  (unordered_multiset<>::reference, const_reference,
>>     >> difference_type): New typedef.
>>     >>  (unordered_multiset<>::get_allocator, empty, size,
>>     >> max_size): Add usings.
>>     >>  (unordered_multiset<>::bucket_count, max_bucket_count,
>>     >> bucket): Add usings.
>>     >>  (unordered_multiset<>::hash_function, key_equal, count,
>>     >> contains): Add usings.
>>     >>  (unordered_multiset<>::rehash, reserve): Add usings.
>>     >>  (unordered_multiset<>::merge): New.
>>     >>               *
>>     >> testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New
>>     test.
>>     >>               *
>>     >> testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New
>>     test.
>>     >>               *
>>     >> testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New
>>     test.
>>     >>               *
>>     >> testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New
>>     test.
>>     >>               *
>>     >>
>>     testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc:
>>     New test.
>>     >>               *
>>     >>
>>     testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc:
>>     New test.
>>     >>               *
>>     >>
>>     testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc:
>>     New test.
>>     >>               *
>>     >>
>>     testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc:
>>     New test.
>>     >>               *
>>     >>
>>     testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc:
>>     New test.
>>     >>               *
>>     >>
>>     testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc:
>>     New test.
>>     >>               *
>>     >>
>>     testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc:
>>     New test.
>>     >>               *
>>     >>
>>     testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc:
>>     New test.
>>     >>               *
>>     >> testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New
>>     test.
>>     >>               *
>>     >> testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New
>>     test.
>>     >>               *
>>     >> testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New
>>     test.
>>     >>               *
>>     >> testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New
>>     test.
>>     >>               * testsuite/util/testsuite_abi.h:
>>     [_GLIBCXX_DEBUG] Use
>>     >> normal unordered container implementation.
>>     >>
>>     >> Tested under Linux x86_64.
>>     >>
>>     >> Ok to commit ?
>>     > Yes, thanks. But ...
>>     >
>>     > This looks like an improvement over what we have now, but not 100%
>>     > correct. The merge functions can exit via exception (if any hash
>>     > function or equality predicate throws), and if that happens the
>>     safe
>>     > iterators will not get invalidated. I think we need to call
>>     > _Base::merge in a try-block, and do the iterator invalidation
>>     whether
>>     > we return normally or via an exception.
>>     >
>>     > Something like:
>>     >
>>     >    template<typename _H2, typename _P2>
>>     >      void
>>     >      merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
>>     >      {
>>     >        struct _Guard
>>     >        {
>>     >          _Guard(unordered_set& __source) noexcept
>>     >          : _M_source(__source), _M_size(__source.size())
>>     >          { }
>>     >
>>     >          ~_Guard()
>>     >          {
>>     >            const size_type __size = _M_source.size();
>>     >            if (__size != _M_size)
>>     >              {
>>     >                if (__size == 0)
>>     >                  _M_source._M_invalidate_all();
>>     >                else
>>     >                  {
>>     >                    auto __pred = [&__source](auto __it)
>>     >                                  { return __source.count(*__it)
>>     == 0; };
>>     > __source._M_invalidate_if(__pred);
>>     > __source._M_invalidate_local_if(__pred);
>>     >                  }
>>     >              }
>>     >          }
>>     >
>>     >          _Guard(const _Guard&) = delete;
>>     >
>>     >          unordered_set& _M_source;
>>     >          const size_type _M_size;
>>     >        };
>>     >        _Guard __guard(__source);
>>     >        _Base::merge(__source._M_base());
>>     >      }
>>     >
>>
>


[-- Attachment #2: merge.patch --]
[-- Type: text/x-patch, Size: 34520 bytes --]

diff --git a/libstdc++-v3/include/bits/hashtable.h b/libstdc++-v3/include/bits/hashtable.h
index 25c45d3ba85..0e949d73614 100644
--- a/libstdc++-v3/include/bits/hashtable.h
+++ b/libstdc++-v3/include/bits/hashtable.h
@@ -1065,14 +1065,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Merge from a compatible container into one with unique keys.
       template<typename _Compatible_Hashtable>
 	void
-	_M_merge_unique(_Compatible_Hashtable& __src) noexcept
+	_M_merge_unique(_Compatible_Hashtable& __src)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  auto __n_elt = __src.size();
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
 	    {
 	      auto __pos = __i++;
 	      const key_type& __k = _ExtractKey{}(*__pos);
@@ -1093,15 +1093,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Merge from a compatible container into one with equivalent keys.
       template<typename _Compatible_Hashtable>
 	void
-	_M_merge_multi(_Compatible_Hashtable& __src) noexcept
+	_M_merge_multi(_Compatible_Hashtable& __src)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  this->reserve(size() + __src.size());
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
-	    _M_reinsert_node_multi(cend(), __src.extract(__i++));
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
+	    {
+	      auto __pos = __i++;
+	      const key_type& __k = _ExtractKey{}(*__pos);
+	      __hash_code __code = this->_M_hash_code(__k);
+	      auto __nh = __src.extract(__pos);
+	      _M_insert_multi_node(nullptr, __code, __nh._M_ptr);
+	      __nh._M_ptr = nullptr;
+	    }
 	}
 #endif // C++17
 
diff --git a/libstdc++-v3/include/bits/hashtable_policy.h b/libstdc++-v3/include/bits/hashtable_policy.h
index 8c72043e368..c0295b75963 100644
--- a/libstdc++-v3/include/bits/hashtable_policy.h
+++ b/libstdc++-v3/include/bits/hashtable_policy.h
@@ -60,19 +60,19 @@ namespace __detail
 
   // Helper function: return distance(first, last) for forward
   // iterators, or 0/1 for input iterators.
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::input_iterator_tag)
     { return __first != __last ? 1 : 0; }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::forward_iterator_tag)
     { return std::distance(__first, __last); }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last)
     { return __distance_fw(__first, __last,
diff --git a/libstdc++-v3/include/debug/safe_container.h b/libstdc++-v3/include/debug/safe_container.h
index 97c47167fe8..5de55d69f34 100644
--- a/libstdc++-v3/include/debug/safe_container.h
+++ b/libstdc++-v3/include/debug/safe_container.h
@@ -78,7 +78,6 @@ namespace __gnu_debug
       { }
 #endif
 
-    public:
       // Copy assignment invalidate all iterators.
       _Safe_container&
       operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT
diff --git a/libstdc++-v3/include/debug/safe_unordered_container.h b/libstdc++-v3/include/debug/safe_unordered_container.h
index aae1e2dab60..ff3fe824683 100644
--- a/libstdc++-v3/include/debug/safe_unordered_container.h
+++ b/libstdc++-v3/include/debug/safe_unordered_container.h
@@ -72,6 +72,106 @@ namespace __gnu_debug
 		{ return __it != __local_end; });
       }
 
+#if __cplusplus > 201402L
+      template<typename _ExtractKey, typename _Source>
+	struct _UContMergeGuard
+	{
+	  _UContMergeGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UContMergeGuard(const _UContMergeGuard&) = delete;
+
+	  ~_UContMergeGuard()
+	  {
+	    const std::size_t __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      { return _M_source.count(_ExtractKey{}(*__it)) == 0; };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const std::size_t _M_size;
+	};
+
+      template<typename _ExtractKey, typename _Source>
+	static _UContMergeGuard<_ExtractKey, _Source>
+	_S_uc_guard(_ExtractKey, _Source& __source)
+	{ return _UContMergeGuard<_ExtractKey, _Source>(__source); }
+
+      template<typename _ExtractKey, typename _Source>
+	struct _UMContMergeGuard
+	{
+	  _UMContMergeGuard(_Source& __source) noexcept
+	  : _M_source(__source), _M_size(__source.size())
+	  { }
+
+	  _UMContMergeGuard(const _UMContMergeGuard&) = delete;
+
+	  ~_UMContMergeGuard()
+	  {
+	    const std::size_t __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    auto __pred =
+		      [this](auto __it)
+		      {
+			auto __rng =
+			  _M_source._M_base().equal_range(_ExtractKey{}(*__it));
+			for (auto __rit = __rng.first;
+			     __rit != __rng.second; ++__rit)
+			  {
+			    if (__it == __rit)
+			      return false;
+			  }
+
+			return true;
+		      };
+		    _M_source._M_invalidate_if(__pred);
+		    _M_source._M_invalidate_local_if(__pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const std::size_t _M_size;
+	};
+
+      template<typename _ExtractKey, typename _Source>
+	static _UMContMergeGuard<_ExtractKey, _Source>
+	_S_umc_guard(_ExtractKey, _Source& __source)
+	{ return _UMContMergeGuard<_ExtractKey, _Source>(__source); }
+#endif // C++17
+
+    public:
       void
       _M_invalidate_all()
       {
diff --git a/libstdc++-v3/include/debug/unordered_map b/libstdc++-v3/include/debug/unordered_map
index bb697d364ea..d6e184a2e99 100644
--- a/libstdc++-v3/include/debug/unordered_map
+++ b/libstdc++-v3/include/debug/unordered_map
@@ -97,7 +97,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_map>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -106,6 +111,7 @@ namespace __debug
 	_Base_local_iterator, unordered_map>		local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_map>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_map() = default;
 
@@ -209,6 +215,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_map& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -291,6 +302,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -298,6 +313,8 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -538,9 +555,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -567,6 +613,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -605,6 +656,9 @@ namespace __debug
 	}
 #endif
 
+      using _Base::operator[];
+      using _Base::at;
+
       size_type
       erase(const key_type& __key)
       {
@@ -651,6 +705,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept	{ return *this; }
 
@@ -843,7 +900,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multimap>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -852,6 +914,7 @@ namespace __debug
 	_Base_local_iterator, unordered_multimap>	local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_multimap>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_multimap() = default;
 
@@ -952,6 +1015,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multimap& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -1034,6 +1102,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -1192,9 +1264,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1221,6 +1322,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -1309,6 +1415,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept { return *this; }
 
diff --git a/libstdc++-v3/include/debug/unordered_set b/libstdc++-v3/include/debug/unordered_set
index c25910946b7..7dc91fa862d 100644
--- a/libstdc++-v3/include/debug/unordered_set
+++ b/libstdc++-v3/include/debug/unordered_set
@@ -88,6 +88,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -95,6 +96,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_set>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -203,6 +208,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_set& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -285,6 +295,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -292,6 +305,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -303,6 +319,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	std::pair<iterator, bool>
 	emplace(_Args&&... __args)
@@ -423,9 +442,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -452,6 +500,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -707,6 +761,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -714,6 +769,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multiset>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -822,6 +881,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multiset& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -904,6 +968,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -911,6 +978,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -922,6 +992,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	iterator
 	emplace(_Args&&... __args)
@@ -1037,9 +1110,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1066,6 +1168,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
new file mode 100644
index 00000000000..6d007944918
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, double>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 3.5 }, { 2, 5.5 }, { 3, 7.5 }, { 5, 11.5 }, { 6, 13.5 } };
+  test_type c1{ { 1, 3.5 }, { 2, 5.5 }, { 3, 7.5 }, { 4, 9.5 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 5.5 );
+  VERIFY( it4->second == 9.5 );
+
+  c0.merge(c1);
+
+  VERIFY( it2->second == 5.5 );
+  VERIFY( it4 != it2 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
new file mode 100644
index 00000000000..543cd960a5e
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
new file mode 100644
index 00000000000..8e234799cbf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
new file mode 100644
index 00000000000..3c9c8268f8c
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
new file mode 100644
index 00000000000..25b3b9e0c75
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
new file mode 100644
index 00000000000..8d28d83b972
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
new file mode 100644
index 00000000000..5db91a27ca0
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
new file mode 100644
index 00000000000..a1636703569
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
new file mode 100644
index 00000000000..bce8da7f6cf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
new file mode 100644
index 00000000000..72317a32e89
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
new file mode 100644
index 00000000000..1b1f4870dd1
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
new file mode 100644
index 00000000000..5005cf8468a
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
new file mode 100644
index 00000000000..8a2bc6e468f
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
new file mode 100644
index 00000000000..3ac96540770
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
new file mode 100644
index 00000000000..7e93b55d507
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
new file mode 100644
index 00000000000..14c8ff63b05
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.h b/libstdc++-v3/testsuite/util/testsuite_abi.h
index 667c46c33d3..4a0cf64f6fb 100644
--- a/libstdc++-v3/testsuite/util/testsuite_abi.h
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
@@ -24,7 +24,11 @@
 #include <locale>
 #if __cplusplus >= 201103L
 # include <unordered_map>
+# ifdef _GLIBCXX_DEBUG
+namespace unord = std::_GLIBCXX_STD_C;
+# else
 namespace unord = std;
+# endif
 #else
 # include <tr1/unordered_map>
 namespace unord = std::tr1;

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-06 13:51         ` François Dumont
@ 2021-11-08 21:36           ` François Dumont
  2021-11-09 16:25             ` Jonathan Wakely
  2021-11-10  0:05             ` H.J. Lu
  0 siblings, 2 replies; 21+ messages in thread
From: François Dumont @ 2021-11-08 21:36 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Jonathan Wakely, libstdc++, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 16174 bytes --]

Yet another version this time with only 1 guard implementation. The 
predicate to invalidate the safe iterators has been externalized.

Ok to commit ?


On 06/11/21 2:51 pm, François Dumont wrote:
> You were right to delay your reply. Here is a new version with less 
> code duplication and a bug fix in the new _UContMergeGuard where we 
> were using it->second rather than it->first to get the key.
>
> Note also that the change to _M_merge_multi implementation is also 
> important because otherwise we might be trying to extract a key from a 
> destructed node.
>
>     libstdc++: [_GLIBCXX_DEBUG] Implement unordered container merge
>
>     The _GLIBCXX_DEBUG unordered containers need a dedicated merge 
> implementation
>     so that any existing iterator on the transfered nodes is properly 
> invalidated.
>
>     Add typedef/using declaration for everything used as-is from 
> normal implementation.
>
>     libstdc++-v3/ChangeLog:
>
>             * include/bits/hashtable_policy.h (__distance_fw): Replace 
> class keyword with
>             typename.
>             * include/bits/hashtable.h 
> (_Hashtable<>::_M_merge_unique): Remove noexcept
>             qualification. Use const_iterator for node 
> extraction/reinsert.
>             (_Hashtable<>::_M_merge_multi): Likewise. Compute new hash 
> code before extract.
>             * include/debug/safe_container.h (_Safe_container<>): Make 
> all methods
>             protected.
>             * include/debug/safe_unordered_container.h
> (_Safe_unordered_container<>::_UContMergeGuard<_ExtractKey, _Source>): 
> New.
> (_Safe_unordered_container<>::_S_uc_guard<_ExtractKey, _Source>): New.
> (_Safe_unordered_container<>::_UMContMergeGuard<_ExtractKey, 
> _Source>): New.
> (_Safe_unordered_container<>::_S_umc_guard<_ExtractKey, _Source>): New.
> (_Safe_unordered_container<>::_M_invalide_all): Make public.
>             (_Safe_unordered_container<>::_M_invalide_if): Likewise.
> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>             * include/debug/unordered_map
>             (unordered_map<>::mapped_type, pointer, const_pointer): 
> New typedef.
>             (unordered_map<>::reference, const_reference, 
> difference_type): New typedef.
>             (unordered_map<>::get_allocator, empty, size, max_size): 
> Add usings.
>             (unordered_map<>::bucket_count, max_bucket_count, bucket): 
> Add usings.
>             (unordered_map<>::hash_function, key_equal, count, 
> contains): Add usings.
>             (unordered_map<>::operator[], at, rehash, reserve): Add 
> usings.
>             (unordered_map<>::merge): New.
>             (unordered_multimap<>::mapped_type, pointer, 
> const_pointer): New typedef.
>             (unordered_multimap<>::reference, const_reference, 
> difference_type): New typedef.
>             (unordered_multimap<>::get_allocator, empty, size, 
> max_size): Add usings.
>             (unordered_multimap<>::bucket_count, max_bucket_count, 
> bucket): Add usings.
>             (unordered_multimap<>::hash_function, key_equal, count, 
> contains): Add usings.
>             (unordered_multimap<>::rehash, reserve): Add usings.
>             (unordered_multimap<>::merge): New.
>             * include/debug/unordered_set
>             (unordered_set<>::mapped_type, pointer, const_pointer): 
> New typedef.
>             (unordered_set<>::reference, const_reference, 
> difference_type): New typedef.
>             (unordered_set<>::get_allocator, empty, size, max_size): 
> Add usings.
>             (unordered_set<>::bucket_count, max_bucket_count, bucket): 
> Add usings.
>             (unordered_set<>::hash_function, key_equal, count, 
> contains): Add usings.
>             (unordered_set<>::rehash, reserve): Add usings.
>             (unordered_set<>::merge): New.
>             (unordered_multiset<>::mapped_type, pointer, 
> const_pointer): New typedef.
>             (unordered_multiset<>::reference, const_reference, 
> difference_type): New typedef.
>             (unordered_multiset<>::get_allocator, empty, size, 
> max_size): Add usings.
>             (unordered_multiset<>::bucket_count, max_bucket_count, 
> bucket): Add usings.
>             (unordered_multiset<>::hash_function, key_equal, count, 
> contains): Add usings.
>             (unordered_multiset<>::rehash, reserve): Add usings.
>             (unordered_multiset<>::merge): New.
>             * 
> testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New test.
>             * 
> testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New test.
>             * testsuite/util/testsuite_abi.h: [_GLIBCXX_DEBUG] Use 
> normal unordered container implementation.
>
> Tested under Linux x86_64.
>
> Ok to commit ?
>
> François
>
> On 25/10/21 8:08 pm, François Dumont wrote:
>> New patch with the proposed workaround below.
>>
>> I also slightly change the _M_merge_multi implementation so that if 
>> the new hash code computation raise an exception the node is simply 
>> not extracted rather than extracted and then released. This way, if 
>> it takes place on the 1st moved node the _GLIBCXX_DEBUG mode won't 
>> try to invalidate anything because the source size won't have changed.
>>
>> Ok to commit ?
>>
>> François
>>
>>
>> On 16/10/21 4:52 pm, Jonathan Wakely wrote:
>>>
>>>
>>> On Sat, 16 Oct 2021, 14:49 François Dumont via Libstdc++, 
>>> <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>>>
>>>     Hi
>>>
>>>          Here is the new proposal. My only concern is that we are
>>>     also using
>>>     hash or equal_to functors in the guard destructor.
>>>
>>>
>>>
>>> Can we catch any exception there, invalidate all iterators, and not 
>>> rethrow the exception?
>>>
>>>
>>>          I am going to enhance merge normal implementation to make
>>>     use of
>>>     the cached hash code when hash functors are the same between the
>>>     source
>>>     and destination of nodes. Maybe I'll be able to make use of it
>>>     in Debug
>>>     implementation too.
>>>
>>>     François
>>>
>>>
>>>     On 14/10/21 10:23 am, Jonathan Wakely wrote:
>>>     > On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
>>>     > <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>>>     >> Hi
>>>     >>
>>>     >>       libstdc++: [_GLIBCXX_DEBUG] Implement unordered
>>>     container merge
>>>     >>
>>>     >>       The _GLIBCXX_DEBUG unordered containers need a
>>>     dedicated merge
>>>     >> implementation
>>>     >>       so that any existing iterator on the transfered nodes
>>>     is properly
>>>     >> invalidated.
>>>     >>
>>>     >>       Add typedef/using declaration for everything used as-is
>>>     from normal
>>>     >> implementation.
>>>     >>
>>>     >>       libstdc++-v3/ChangeLog:
>>>     >>
>>>     >>               * include/debug/safe_container.h
>>>     (_Safe_container<>): Make
>>>     >> all methods
>>>     >>               protected.
>>>     >>               * include/debug/safe_unordered_container.h
>>>     >>  (_Safe_unordered_container<>::_M_invalide_all): Make public.
>>>     >>  (_Safe_unordered_container<>::_M_invalide_if): Likewise.
>>>     >> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>>>     >>               * include/debug/unordered_map
>>>     >>  (unordered_map<>::mapped_type, pointer, const_pointer): New
>>>     >> typedef.
>>>     >>  (unordered_map<>::reference, const_reference,
>>>     >> difference_type): New typedef.
>>>     >>  (unordered_map<>::get_allocator, empty, size, max_size):
>>>     >> Add usings.
>>>     >>  (unordered_map<>::bucket_count, max_bucket_count, bucket):
>>>     >> Add usings.
>>>     >>  (unordered_map<>::hash_function, key_equal, count,
>>>     >> contains): Add usings.
>>>     >>  (unordered_map<>::operator[], at, rehash, reserve): Add usings.
>>>     >>               (unordered_map<>::merge): New.
>>>     >>  (unordered_multimap<>::mapped_type, pointer,
>>>     >> const_pointer): New typedef.
>>>     >>  (unordered_multimap<>::reference, const_reference,
>>>     >> difference_type): New typedef.
>>>     >>  (unordered_multimap<>::get_allocator, empty, size,
>>>     >> max_size): Add usings.
>>>     >>  (unordered_multimap<>::bucket_count, max_bucket_count,
>>>     >> bucket): Add usings.
>>>     >>  (unordered_multimap<>::hash_function, key_equal, count,
>>>     >> contains): Add usings.
>>>     >>  (unordered_multimap<>::rehash, reserve): Add usings.
>>>     >>  (unordered_multimap<>::merge): New.
>>>     >>               * include/debug/unordered_set
>>>     >>  (unordered_set<>::mapped_type, pointer, const_pointer): New
>>>     >> typedef.
>>>     >>  (unordered_set<>::reference, const_reference,
>>>     >> difference_type): New typedef.
>>>     >>  (unordered_set<>::get_allocator, empty, size, max_size):
>>>     >> Add usings.
>>>     >>  (unordered_set<>::bucket_count, max_bucket_count, bucket):
>>>     >> Add usings.
>>>     >>  (unordered_set<>::hash_function, key_equal, count,
>>>     >> contains): Add usings.
>>>     >>               (unordered_set<>::rehash, reserve): Add usings.
>>>     >>               (unordered_set<>::merge): New.
>>>     >>  (unordered_multiset<>::mapped_type, pointer,
>>>     >> const_pointer): New typedef.
>>>     >>  (unordered_multiset<>::reference, const_reference,
>>>     >> difference_type): New typedef.
>>>     >>  (unordered_multiset<>::get_allocator, empty, size,
>>>     >> max_size): Add usings.
>>>     >>  (unordered_multiset<>::bucket_count, max_bucket_count,
>>>     >> bucket): Add usings.
>>>     >>  (unordered_multiset<>::hash_function, key_equal, count,
>>>     >> contains): Add usings.
>>>     >>  (unordered_multiset<>::rehash, reserve): Add usings.
>>>     >>  (unordered_multiset<>::merge): New.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_map/debug/merge1_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_map/debug/merge2_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_map/debug/merge3_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_map/debug/merge4_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >>
>>>     testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_set/debug/merge1_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_set/debug/merge2_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_set/debug/merge3_neg.cc:
>>>     New test.
>>>     >>               *
>>>     >> testsuite/23_containers/unordered_set/debug/merge4_neg.cc:
>>>     New test.
>>>     >>               * testsuite/util/testsuite_abi.h:
>>>     [_GLIBCXX_DEBUG] Use
>>>     >> normal unordered container implementation.
>>>     >>
>>>     >> Tested under Linux x86_64.
>>>     >>
>>>     >> Ok to commit ?
>>>     > Yes, thanks. But ...
>>>     >
>>>     > This looks like an improvement over what we have now, but not 100%
>>>     > correct. The merge functions can exit via exception (if any hash
>>>     > function or equality predicate throws), and if that happens
>>>     the safe
>>>     > iterators will not get invalidated. I think we need to call
>>>     > _Base::merge in a try-block, and do the iterator invalidation
>>>     whether
>>>     > we return normally or via an exception.
>>>     >
>>>     > Something like:
>>>     >
>>>     >    template<typename _H2, typename _P2>
>>>     >      void
>>>     >      merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
>>>     >      {
>>>     >        struct _Guard
>>>     >        {
>>>     >          _Guard(unordered_set& __source) noexcept
>>>     >          : _M_source(__source), _M_size(__source.size())
>>>     >          { }
>>>     >
>>>     >          ~_Guard()
>>>     >          {
>>>     >            const size_type __size = _M_source.size();
>>>     >            if (__size != _M_size)
>>>     >              {
>>>     >                if (__size == 0)
>>>     >                  _M_source._M_invalidate_all();
>>>     >                else
>>>     >                  {
>>>     >                    auto __pred = [&__source](auto __it)
>>>     >                                  { return
>>>     __source.count(*__it) == 0; };
>>>     > __source._M_invalidate_if(__pred);
>>>     > __source._M_invalidate_local_if(__pred);
>>>     >                  }
>>>     >              }
>>>     >          }
>>>     >
>>>     >          _Guard(const _Guard&) = delete;
>>>     >
>>>     >          unordered_set& _M_source;
>>>     >          const size_type _M_size;
>>>     >        };
>>>     >        _Guard __guard(__source);
>>>     >        _Base::merge(__source._M_base());
>>>     >      }
>>>     >
>>>
>>
>


[-- Attachment #2: merge.patch --]
[-- Type: text/x-patch, Size: 34367 bytes --]

diff --git a/libstdc++-v3/include/bits/hashtable.h b/libstdc++-v3/include/bits/hashtable.h
index 25c45d3ba85..0e949d73614 100644
--- a/libstdc++-v3/include/bits/hashtable.h
+++ b/libstdc++-v3/include/bits/hashtable.h
@@ -1065,14 +1065,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Merge from a compatible container into one with unique keys.
       template<typename _Compatible_Hashtable>
 	void
-	_M_merge_unique(_Compatible_Hashtable& __src) noexcept
+	_M_merge_unique(_Compatible_Hashtable& __src)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  auto __n_elt = __src.size();
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
 	    {
 	      auto __pos = __i++;
 	      const key_type& __k = _ExtractKey{}(*__pos);
@@ -1093,15 +1093,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       /// Merge from a compatible container into one with equivalent keys.
       template<typename _Compatible_Hashtable>
 	void
-	_M_merge_multi(_Compatible_Hashtable& __src) noexcept
+	_M_merge_multi(_Compatible_Hashtable& __src)
 	{
 	  static_assert(is_same_v<typename _Compatible_Hashtable::node_type,
 	      node_type>, "Node types are compatible");
 	  __glibcxx_assert(get_allocator() == __src.get_allocator());
 
 	  this->reserve(size() + __src.size());
-	  for (auto __i = __src.begin(), __end = __src.end(); __i != __end;)
-	    _M_reinsert_node_multi(cend(), __src.extract(__i++));
+	  for (auto __i = __src.cbegin(), __end = __src.cend(); __i != __end;)
+	    {
+	      auto __pos = __i++;
+	      const key_type& __k = _ExtractKey{}(*__pos);
+	      __hash_code __code = this->_M_hash_code(__k);
+	      auto __nh = __src.extract(__pos);
+	      _M_insert_multi_node(nullptr, __code, __nh._M_ptr);
+	      __nh._M_ptr = nullptr;
+	    }
 	}
 #endif // C++17
 
diff --git a/libstdc++-v3/include/bits/hashtable_policy.h b/libstdc++-v3/include/bits/hashtable_policy.h
index 8c72043e368..c0295b75963 100644
--- a/libstdc++-v3/include/bits/hashtable_policy.h
+++ b/libstdc++-v3/include/bits/hashtable_policy.h
@@ -60,19 +60,19 @@ namespace __detail
 
   // Helper function: return distance(first, last) for forward
   // iterators, or 0/1 for input iterators.
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::input_iterator_tag)
     { return __first != __last ? 1 : 0; }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last,
 		  std::forward_iterator_tag)
     { return std::distance(__first, __last); }
 
-  template<class _Iterator>
+  template<typename _Iterator>
     inline typename std::iterator_traits<_Iterator>::difference_type
     __distance_fw(_Iterator __first, _Iterator __last)
     { return __distance_fw(__first, __last,
diff --git a/libstdc++-v3/include/debug/safe_container.h b/libstdc++-v3/include/debug/safe_container.h
index 97c47167fe8..5de55d69f34 100644
--- a/libstdc++-v3/include/debug/safe_container.h
+++ b/libstdc++-v3/include/debug/safe_container.h
@@ -78,7 +78,6 @@ namespace __gnu_debug
       { }
 #endif
 
-    public:
       // Copy assignment invalidate all iterators.
       _Safe_container&
       operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT
diff --git a/libstdc++-v3/include/debug/safe_unordered_container.h b/libstdc++-v3/include/debug/safe_unordered_container.h
index aae1e2dab60..ce9d9ea4091 100644
--- a/libstdc++-v3/include/debug/safe_unordered_container.h
+++ b/libstdc++-v3/include/debug/safe_unordered_container.h
@@ -72,6 +72,96 @@ namespace __gnu_debug
 		{ return __it != __local_end; });
       }
 
+#if __cplusplus > 201402L
+      template<typename _ExtractKey, typename _Source>
+	struct _UContInvalidatePred
+	{
+	  template<typename _Iterator>
+	    bool
+	    operator()(_Iterator __it) const
+	    { return _M_source.count(_ExtractKey{}(*__it)) == 0; }
+
+	  const _Source& _M_source;
+	};
+
+      template<typename _ExtractKey, typename _Source>
+	struct _UMContInvalidatePred
+	{
+	  template<typename _Iterator>
+	    bool
+	    operator()(_Iterator __it) const
+	    {
+	      auto __rng =
+		_M_source._M_base().equal_range(_ExtractKey{}(*__it));
+	      for (auto __rit = __rng.first;
+		   __rit != __rng.second; ++__rit)
+		{
+		  if (__it == __rit)
+		    return false;
+		}
+
+	      return true;
+	    }
+
+	  const _Source& _M_source;
+	};
+
+      template<typename _Source, typename _InvalidatePred>
+	struct _UContMergeGuard
+	{
+	  _UContMergeGuard(_Source& __src) noexcept
+	  : _M_source(__src), _M_size(__src.size()), _M_pred { __src }
+	  { }
+
+	  _UContMergeGuard(const _UContMergeGuard&) = delete;
+
+	  ~_UContMergeGuard()
+	  {
+	    const std::size_t __size = _M_source.size();
+	    if (__size == _M_size)
+	      return;
+
+	    __try
+	      {
+		if (__size == 0)
+		  _M_source._M_invalidate_all();
+		else
+		  {
+		    _M_source._M_invalidate_if(_M_pred);
+		    _M_source._M_invalidate_local_if(_M_pred);
+		  }
+	      }
+	    __catch(...)
+	      {
+		_M_source._M_invalidate_all();
+	      }
+	  }
+
+	  _Source& _M_source;
+	  const std::size_t _M_size;
+	  _InvalidatePred _M_pred;
+	};
+
+      template<typename _ExtractKey, typename _Source>
+	static _UContMergeGuard<_Source,
+				_UContInvalidatePred<_ExtractKey, _Source>>
+	_S_uc_guard(_ExtractKey, _Source& __src)
+	{
+	  typedef _UContInvalidatePred<_ExtractKey, _Source> _InvalidatePred;
+	  return _UContMergeGuard<_Source, _InvalidatePred>(__src);
+	}
+
+      template<typename _ExtractKey, typename _Source>
+	static _UContMergeGuard<_Source,
+				_UMContInvalidatePred<_ExtractKey, _Source>>
+	_S_umc_guard(_ExtractKey, _Source& __src)
+	{
+	  typedef _UMContInvalidatePred<_ExtractKey, _Source> _InvalidatePred;
+	  return _UContMergeGuard<_Source, _InvalidatePred>(__src);
+	}
+#endif // C++17
+
+    public:
       void
       _M_invalidate_all()
       {
diff --git a/libstdc++-v3/include/debug/unordered_map b/libstdc++-v3/include/debug/unordered_map
index bb697d364ea..d6e184a2e99 100644
--- a/libstdc++-v3/include/debug/unordered_map
+++ b/libstdc++-v3/include/debug/unordered_map
@@ -97,7 +97,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_map>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -106,6 +111,7 @@ namespace __debug
 	_Base_local_iterator, unordered_map>		local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_map>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_map() = default;
 
@@ -209,6 +215,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_map& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -291,6 +302,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -298,6 +313,8 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -538,9 +555,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -567,6 +613,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -605,6 +656,9 @@ namespace __debug
 	}
 #endif
 
+      using _Base::operator[];
+      using _Base::at;
+
       size_type
       erase(const key_type& __key)
       {
@@ -651,6 +705,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept	{ return *this; }
 
@@ -843,7 +900,12 @@ namespace __debug
 
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
+      typedef typename _Base::mapped_type		mapped_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multimap>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -852,6 +914,7 @@ namespace __debug
 	_Base_local_iterator, unordered_multimap>	local_iterator;
       typedef __gnu_debug::_Safe_local_iterator<
 	_Base_const_local_iterator, unordered_multimap>	const_local_iterator;
+      typedef typename _Base::difference_type		difference_type;
 
       unordered_multimap() = default;
 
@@ -952,6 +1015,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multimap& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -1034,6 +1102,10 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+      using _Base::bucket;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -1192,9 +1264,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multimap<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Select1st{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_map<_Key, _Tp, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1221,6 +1322,11 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -1309,6 +1415,9 @@ namespace __debug
 	return { __next, this };
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       _Base&
       _M_base() noexcept { return *this; }
 
diff --git a/libstdc++-v3/include/debug/unordered_set b/libstdc++-v3/include/debug/unordered_set
index c25910946b7..7dc91fa862d 100644
--- a/libstdc++-v3/include/debug/unordered_set
+++ b/libstdc++-v3/include/debug/unordered_set
@@ -88,6 +88,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -95,6 +96,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_set>			iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -203,6 +208,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_set& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -285,6 +295,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -292,6 +305,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -303,6 +319,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	std::pair<iterator, bool>
 	emplace(_Args&&... __args)
@@ -423,9 +442,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -452,6 +500,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
@@ -707,6 +761,7 @@ namespace __debug
 
     public:
       typedef typename _Base::size_type			size_type;
+      typedef typename _Base::difference_type		difference_type;
       typedef typename _Base::hasher			hasher;
       typedef typename _Base::key_equal			key_equal;
       typedef typename _Base::allocator_type		allocator_type;
@@ -714,6 +769,10 @@ namespace __debug
       typedef typename _Base::key_type			key_type;
       typedef typename _Base::value_type		value_type;
 
+      typedef typename _Base::pointer			pointer;
+      typedef typename _Base::const_pointer		const_pointer;
+      typedef typename _Base::reference			reference;
+      typedef typename _Base::const_reference		const_reference;
       typedef __gnu_debug::_Safe_iterator<
 	_Base_iterator, unordered_multiset>		iterator;
       typedef __gnu_debug::_Safe_iterator<
@@ -822,6 +881,11 @@ namespace __debug
 	return *this;
       }
 
+      using _Base::get_allocator;
+      using _Base::empty;
+      using _Base::size;
+      using _Base::max_size;
+
       void
       swap(unordered_multiset& __x)
 	noexcept( noexcept(declval<_Base&>().swap(__x)) )
@@ -904,6 +968,9 @@ namespace __debug
 	return { _Base::cend(__b), this };
       }
 
+      using _Base::bucket_count;
+      using _Base::max_bucket_count;
+
       size_type
       bucket_size(size_type __b) const
       {
@@ -911,6 +978,9 @@ namespace __debug
 	return _Base::bucket_size(__b);
       }
 
+      using _Base::bucket;
+      using _Base::load_factor;
+
       float
       max_load_factor() const noexcept
       { return _Base::max_load_factor(); }
@@ -922,6 +992,9 @@ namespace __debug
 	_Base::max_load_factor(__f);
       }
 
+      using _Base::rehash;
+      using _Base::reserve;
+
       template<typename... _Args>
 	iterator
 	emplace(_Args&&... __args)
@@ -1037,9 +1110,38 @@ namespace __debug
 	return { _Base::insert(__hint.base(), std::move(__nh)), this };
       }
 
-      using _Base::merge;
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_umc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_multiset<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
+	{
+	  auto __guard
+	    = _Safe::_S_uc_guard(std::__detail::_Identity{}, __source);
+	  _Base::merge(__source._M_base());
+	}
+
+      template<typename _H2, typename _P2>
+	void
+	merge(unordered_set<_Value, _H2, _P2, _Alloc>&& __source)
+	{ merge(__source); }
 #endif // C++17
 
+      using _Base::hash_function;
+      using _Base::key_eq;
+
       iterator
       find(const key_type& __key)
       { return { _Base::find(__key), this }; }
@@ -1066,6 +1168,12 @@ namespace __debug
 	{ return { _Base::find(__k), this }; }
 #endif
 
+      using _Base::count;
+
+#if __cplusplus > 201703L
+      using _Base::contains;
+#endif
+
       std::pair<iterator, iterator>
       equal_range(const key_type& __key)
       {
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
new file mode 100644
index 00000000000..6d007944918
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, double>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 3.5 }, { 2, 5.5 }, { 3, 7.5 }, { 5, 11.5 }, { 6, 13.5 } };
+  test_type c1{ { 1, 3.5 }, { 2, 5.5 }, { 3, 7.5 }, { 4, 9.5 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 5.5 );
+  VERIFY( it4->second == 9.5 );
+
+  c0.merge(c1);
+
+  VERIFY( it2->second == 5.5 );
+  VERIFY( it4 != it2 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
new file mode 100644
index 00000000000..543cd960a5e
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 5, 5 }, { 6, 6 } };
+  test_type c1{ { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 } };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( it2->second == 2 );
+  VERIFY( it4->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it2->second == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
new file mode 100644
index 00000000000..8e234799cbf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge3_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
new file mode 100644
index 00000000000..3c9c8268f8c
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_map/debug/merge4_neg.cc
@@ -0,0 +1,42 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_map<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 2, 2 }, { 3, 3 },
+     { 5, 5 }, { 6, 6 }, { 7, 7 }
+    };
+  std::unordered_multimap<int, int> c1
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 }, { 2, 2 },
+     { 3, 3 }, { 3, 3 }, { 4, 4 }, { 4, 4 },
+     { 5, 5 }
+    };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( it42->second == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it1->second == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
new file mode 100644
index 00000000000..25b3b9e0c75
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
new file mode 100644
index 00000000000..8d28d83b972
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
new file mode 100644
index 00000000000..5db91a27ca0
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
new file mode 100644
index 00000000000..a1636703569
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc
@@ -0,0 +1,32 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_map>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multimap<int, int>;
+
+void
+test01()
+{
+  test_type c0
+    {
+     { 1, 1 }, { 1, 1 }, { 2, 2 },
+     { 2, 2 }, { 3, 3 }, { 3, 3 }
+    };
+  std::unordered_map<int, int> c1{ { 1, 1 }, { 2, 2 }, { 3, 3 } };
+
+  auto it = c1.find(2);
+  VERIFY( it->second == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
new file mode 100644
index 00000000000..bce8da7f6cf
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
new file mode 100644
index 00000000000..72317a32e89
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  test_type c1 = c0;
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
new file mode 100644
index 00000000000..1b1f4870dd1
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(c1);
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
new file mode 100644
index 00000000000..5005cf8468a
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc
@@ -0,0 +1,28 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_multiset<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 1, 2, 2, 3, 3 };
+  std::unordered_set<int> c1{ 1, 2, 3 };
+
+  auto it = c1.find(2);
+  VERIFY( *it == 2 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( it != c1.end() ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
new file mode 100644
index 00000000000..8a2bc6e468f
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge1_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
new file mode 100644
index 00000000000..3ac96540770
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge2_neg.cc
@@ -0,0 +1,31 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6 };
+  test_type c1{ 1, 2, 3, 4 };
+
+  auto it2 = c1.find(2);
+  auto it4 = c1.find(4);
+  VERIFY( *it2 == 2 );
+  VERIFY( *it4 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it2 == 2 );
+  VERIFY( it2 != it4 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
new file mode 100644
index 00000000000..7e93b55d507
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge3_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(c1);
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
new file mode 100644
index 00000000000..14c8ff63b05
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/unordered_set/debug/merge4_neg.cc
@@ -0,0 +1,33 @@
+// { dg-do run { target c++17 xfail *-*-* } }
+// { dg-require-debug-mode "" }
+
+#include <unordered_set>
+#include <algorithm>
+#include <testsuite_hooks.h>
+
+using test_type = std::unordered_set<int>;
+
+void
+test01()
+{
+  test_type c0{ 1, 2, 3, 5, 6, 7 };
+  std::unordered_multiset<int> c1{ 1, 1, 2, 2, 3, 3, 4, 4, 5 };
+
+  auto it1 = c1.find(1);
+  auto it41 = c1.find(4);
+  auto it42 = it41;
+  ++it42;
+  VERIFY( *it42 == 4 );
+
+  c0.merge(std::move(c1));
+
+  VERIFY( *it1 == 1 );
+  VERIFY( c1.count(4) == 1 );
+  VERIFY( it41 != it42 ); // Invalid iterator.
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.h b/libstdc++-v3/testsuite/util/testsuite_abi.h
index 667c46c33d3..4a0cf64f6fb 100644
--- a/libstdc++-v3/testsuite/util/testsuite_abi.h
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
@@ -24,7 +24,11 @@
 #include <locale>
 #if __cplusplus >= 201103L
 # include <unordered_map>
+# ifdef _GLIBCXX_DEBUG
+namespace unord = std::_GLIBCXX_STD_C;
+# else
 namespace unord = std;
+# endif
 #else
 # include <tr1/unordered_map>
 namespace unord = std::tr1;

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-08 21:36           ` François Dumont
@ 2021-11-09 16:25             ` Jonathan Wakely
  2021-11-10  5:47               ` François Dumont
  2021-11-10 11:55               ` Jonathan Wakely
  2021-11-10  0:05             ` H.J. Lu
  1 sibling, 2 replies; 21+ messages in thread
From: Jonathan Wakely @ 2021-11-09 16:25 UTC (permalink / raw)
  To: François Dumont; +Cc: Jonathan Wakely, libstdc++, gcc-patches

On Mon, 8 Nov 2021 at 21:36, François Dumont <frs.dumont@gmail.com> wrote:

> Yet another version this time with only 1 guard implementation. The
> predicate to invalidate the safe iterators has been externalized.
>
> Ok to commit ?
>

I like this version a lot - thanks for persisting with it.

OK to commit, thanks.


As an aside ...

--- a/libstdc++-v3/testsuite/util/testsuite_abi.h
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
@@ -24,7 +24,11 @@
 #include <locale>
 #if __cplusplus >= 201103L
 # include <unordered_map>
+# ifdef _GLIBCXX_DEBUG
+namespace unord = std::_GLIBCXX_STD_C;
+# else
 namespace unord = std;
+# endif
 #else
 # include <tr1/unordered_map>
 namespace unord = std::tr1;


Several times I've been annoyed by the fact that we don't have a way to
refer to std::_GLIBCXX_STD_C::vector etc. that is always valid, in normal
mode and debug mode.

Maybe we should add:

namespace std { namespace _GLIBCXX_STD_C = ::std; }

That way we can refer to std::_GLIBCXX_STD_C::foo in normal mode, and it
will mean the same thing as in debug mode. So we don't need to use #if
conditions like this.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-08 21:36           ` François Dumont
  2021-11-09 16:25             ` Jonathan Wakely
@ 2021-11-10  0:05             ` H.J. Lu
  2021-11-10  5:44               ` François Dumont
  1 sibling, 1 reply; 21+ messages in thread
From: H.J. Lu @ 2021-11-10  0:05 UTC (permalink / raw)
  To: François Dumont
  Cc: Jonathan Wakely, Jonathan Wakely, libstdc++, gcc-patches

On Mon, Nov 8, 2021 at 1:37 PM François Dumont via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> Yet another version this time with only 1 guard implementation. The
> predicate to invalidate the safe iterators has been externalized.
>
> Ok to commit ?
>

This may have broken GCC bootstrap on Linux/x86-64:

https://gcc.gnu.org/pipermail/gcc-regression/2021-November/075734.html

In file included from ../../src-master/gcc/sanopt.c:22:
In member function ‘hash_table<Descriptor, Lazy,
Allocator>::value_type* hash_table<Descriptor, Lazy,
Allocator>::alloc_entries(size_t) const [with Descriptor =
hash_map<tree_node*, auto_vec<gimple*> >::hash_entry; bool Lazy =
false; Allocator = xcallocator]’,
    inlined from ‘void hash_table<Descriptor, Lazy,
Allocator>::expand() [with Descriptor = hash_map<tree_node*,
auto_vec<gimple*> >::hash_entry; bool Lazy = false; Allocator =
xcallocator]’ at ../../src-master/gcc/hash-table.h:802:40:
../../src-master/gcc/system.h:784:34: error: section type conflict
with ‘void hash_table<Descriptor, Lazy, Allocator>::expand() [with
Descriptor = hash_map<tree_node*, auto_vec<gimple*> >::hash_entry;
bool Lazy = false; Allocator = xcallocator]’
  784 |    ((void)(!(EXPR) ? fancy_abort (__FILE__, __LINE__,
__FUNCTION__), 0 : 0))
      |                      ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../../src-master/gcc/hash-table.h:715:3: note: in expansion of macro
‘gcc_assert’
  715 |   gcc_assert (nentries != NULL);
      |   ^~~~~~~~~~
In file included from ../../src-master/gcc/coretypes.h:482,
                 from ../../src-master/gcc/sanopt.c:23:
../../src-master/gcc/hash-table.h: In member function ‘void
hash_table<Descriptor, Lazy, Allocator>::expand() [with Descriptor =
hash_map<tree_node*, auto_vec<gimple*> >::hash_entry; bool Lazy =
false; Allocator = xcallocator]’:
../../src-master/gcc/hash-table.h:779:1: note: ‘void
hash_table<Descriptor, Lazy, Allocator>::expand() [with Descriptor =
hash_map<tree_node*, auto_vec<gimple*> >::hash_entry; bool Lazy =
false; Allocator = xcallocator]’ was declared here
  779 | hash_table<Descriptor, Lazy, Allocator>::expand ()
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

> On 06/11/21 2:51 pm, François Dumont wrote:
> > You were right to delay your reply. Here is a new version with less
> > code duplication and a bug fix in the new _UContMergeGuard where we
> > were using it->second rather than it->first to get the key.
> >
> > Note also that the change to _M_merge_multi implementation is also
> > important because otherwise we might be trying to extract a key from a
> > destructed node.
> >
> >     libstdc++: [_GLIBCXX_DEBUG] Implement unordered container merge
> >
> >     The _GLIBCXX_DEBUG unordered containers need a dedicated merge
> > implementation
> >     so that any existing iterator on the transfered nodes is properly
> > invalidated.
> >
> >     Add typedef/using declaration for everything used as-is from
> > normal implementation.
> >
> >     libstdc++-v3/ChangeLog:
> >
> >             * include/bits/hashtable_policy.h (__distance_fw): Replace
> > class keyword with
> >             typename.
> >             * include/bits/hashtable.h
> > (_Hashtable<>::_M_merge_unique): Remove noexcept
> >             qualification. Use const_iterator for node
> > extraction/reinsert.
> >             (_Hashtable<>::_M_merge_multi): Likewise. Compute new hash
> > code before extract.
> >             * include/debug/safe_container.h (_Safe_container<>): Make
> > all methods
> >             protected.
> >             * include/debug/safe_unordered_container.h
> > (_Safe_unordered_container<>::_UContMergeGuard<_ExtractKey, _Source>):
> > New.
> > (_Safe_unordered_container<>::_S_uc_guard<_ExtractKey, _Source>): New.
> > (_Safe_unordered_container<>::_UMContMergeGuard<_ExtractKey,
> > _Source>): New.
> > (_Safe_unordered_container<>::_S_umc_guard<_ExtractKey, _Source>): New.
> > (_Safe_unordered_container<>::_M_invalide_all): Make public.
> >             (_Safe_unordered_container<>::_M_invalide_if): Likewise.
> > (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
> >             * include/debug/unordered_map
> >             (unordered_map<>::mapped_type, pointer, const_pointer):
> > New typedef.
> >             (unordered_map<>::reference, const_reference,
> > difference_type): New typedef.
> >             (unordered_map<>::get_allocator, empty, size, max_size):
> > Add usings.
> >             (unordered_map<>::bucket_count, max_bucket_count, bucket):
> > Add usings.
> >             (unordered_map<>::hash_function, key_equal, count,
> > contains): Add usings.
> >             (unordered_map<>::operator[], at, rehash, reserve): Add
> > usings.
> >             (unordered_map<>::merge): New.
> >             (unordered_multimap<>::mapped_type, pointer,
> > const_pointer): New typedef.
> >             (unordered_multimap<>::reference, const_reference,
> > difference_type): New typedef.
> >             (unordered_multimap<>::get_allocator, empty, size,
> > max_size): Add usings.
> >             (unordered_multimap<>::bucket_count, max_bucket_count,
> > bucket): Add usings.
> >             (unordered_multimap<>::hash_function, key_equal, count,
> > contains): Add usings.
> >             (unordered_multimap<>::rehash, reserve): Add usings.
> >             (unordered_multimap<>::merge): New.
> >             * include/debug/unordered_set
> >             (unordered_set<>::mapped_type, pointer, const_pointer):
> > New typedef.
> >             (unordered_set<>::reference, const_reference,
> > difference_type): New typedef.
> >             (unordered_set<>::get_allocator, empty, size, max_size):
> > Add usings.
> >             (unordered_set<>::bucket_count, max_bucket_count, bucket):
> > Add usings.
> >             (unordered_set<>::hash_function, key_equal, count,
> > contains): Add usings.
> >             (unordered_set<>::rehash, reserve): Add usings.
> >             (unordered_set<>::merge): New.
> >             (unordered_multiset<>::mapped_type, pointer,
> > const_pointer): New typedef.
> >             (unordered_multiset<>::reference, const_reference,
> > difference_type): New typedef.
> >             (unordered_multiset<>::get_allocator, empty, size,
> > max_size): Add usings.
> >             (unordered_multiset<>::bucket_count, max_bucket_count,
> > bucket): Add usings.
> >             (unordered_multiset<>::hash_function, key_equal, count,
> > contains): Add usings.
> >             (unordered_multiset<>::rehash, reserve): Add usings.
> >             (unordered_multiset<>::merge): New.
> >             *
> > testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New test.
> >             *
> > testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New test.
> >             * testsuite/util/testsuite_abi.h: [_GLIBCXX_DEBUG] Use
> > normal unordered container implementation.
> >
> > Tested under Linux x86_64.
> >
> > Ok to commit ?
> >
> > François
> >
> > On 25/10/21 8:08 pm, François Dumont wrote:
> >> New patch with the proposed workaround below.
> >>
> >> I also slightly change the _M_merge_multi implementation so that if
> >> the new hash code computation raise an exception the node is simply
> >> not extracted rather than extracted and then released. This way, if
> >> it takes place on the 1st moved node the _GLIBCXX_DEBUG mode won't
> >> try to invalidate anything because the source size won't have changed.
> >>
> >> Ok to commit ?
> >>
> >> François
> >>
> >>
> >> On 16/10/21 4:52 pm, Jonathan Wakely wrote:
> >>>
> >>>
> >>> On Sat, 16 Oct 2021, 14:49 François Dumont via Libstdc++,
> >>> <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
> >>>
> >>>     Hi
> >>>
> >>>          Here is the new proposal. My only concern is that we are
> >>>     also using
> >>>     hash or equal_to functors in the guard destructor.
> >>>
> >>>
> >>>
> >>> Can we catch any exception there, invalidate all iterators, and not
> >>> rethrow the exception?
> >>>
> >>>
> >>>          I am going to enhance merge normal implementation to make
> >>>     use of
> >>>     the cached hash code when hash functors are the same between the
> >>>     source
> >>>     and destination of nodes. Maybe I'll be able to make use of it
> >>>     in Debug
> >>>     implementation too.
> >>>
> >>>     François
> >>>
> >>>
> >>>     On 14/10/21 10:23 am, Jonathan Wakely wrote:
> >>>     > On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
> >>>     > <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
> >>>     >> Hi
> >>>     >>
> >>>     >>       libstdc++: [_GLIBCXX_DEBUG] Implement unordered
> >>>     container merge
> >>>     >>
> >>>     >>       The _GLIBCXX_DEBUG unordered containers need a
> >>>     dedicated merge
> >>>     >> implementation
> >>>     >>       so that any existing iterator on the transfered nodes
> >>>     is properly
> >>>     >> invalidated.
> >>>     >>
> >>>     >>       Add typedef/using declaration for everything used as-is
> >>>     from normal
> >>>     >> implementation.
> >>>     >>
> >>>     >>       libstdc++-v3/ChangeLog:
> >>>     >>
> >>>     >>               * include/debug/safe_container.h
> >>>     (_Safe_container<>): Make
> >>>     >> all methods
> >>>     >>               protected.
> >>>     >>               * include/debug/safe_unordered_container.h
> >>>     >>  (_Safe_unordered_container<>::_M_invalide_all): Make public.
> >>>     >>  (_Safe_unordered_container<>::_M_invalide_if): Likewise.
> >>>     >> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
> >>>     >>               * include/debug/unordered_map
> >>>     >>  (unordered_map<>::mapped_type, pointer, const_pointer): New
> >>>     >> typedef.
> >>>     >>  (unordered_map<>::reference, const_reference,
> >>>     >> difference_type): New typedef.
> >>>     >>  (unordered_map<>::get_allocator, empty, size, max_size):
> >>>     >> Add usings.
> >>>     >>  (unordered_map<>::bucket_count, max_bucket_count, bucket):
> >>>     >> Add usings.
> >>>     >>  (unordered_map<>::hash_function, key_equal, count,
> >>>     >> contains): Add usings.
> >>>     >>  (unordered_map<>::operator[], at, rehash, reserve): Add usings.
> >>>     >>               (unordered_map<>::merge): New.
> >>>     >>  (unordered_multimap<>::mapped_type, pointer,
> >>>     >> const_pointer): New typedef.
> >>>     >>  (unordered_multimap<>::reference, const_reference,
> >>>     >> difference_type): New typedef.
> >>>     >>  (unordered_multimap<>::get_allocator, empty, size,
> >>>     >> max_size): Add usings.
> >>>     >>  (unordered_multimap<>::bucket_count, max_bucket_count,
> >>>     >> bucket): Add usings.
> >>>     >>  (unordered_multimap<>::hash_function, key_equal, count,
> >>>     >> contains): Add usings.
> >>>     >>  (unordered_multimap<>::rehash, reserve): Add usings.
> >>>     >>  (unordered_multimap<>::merge): New.
> >>>     >>               * include/debug/unordered_set
> >>>     >>  (unordered_set<>::mapped_type, pointer, const_pointer): New
> >>>     >> typedef.
> >>>     >>  (unordered_set<>::reference, const_reference,
> >>>     >> difference_type): New typedef.
> >>>     >>  (unordered_set<>::get_allocator, empty, size, max_size):
> >>>     >> Add usings.
> >>>     >>  (unordered_set<>::bucket_count, max_bucket_count, bucket):
> >>>     >> Add usings.
> >>>     >>  (unordered_set<>::hash_function, key_equal, count,
> >>>     >> contains): Add usings.
> >>>     >>               (unordered_set<>::rehash, reserve): Add usings.
> >>>     >>               (unordered_set<>::merge): New.
> >>>     >>  (unordered_multiset<>::mapped_type, pointer,
> >>>     >> const_pointer): New typedef.
> >>>     >>  (unordered_multiset<>::reference, const_reference,
> >>>     >> difference_type): New typedef.
> >>>     >>  (unordered_multiset<>::get_allocator, empty, size,
> >>>     >> max_size): Add usings.
> >>>     >>  (unordered_multiset<>::bucket_count, max_bucket_count,
> >>>     >> bucket): Add usings.
> >>>     >>  (unordered_multiset<>::hash_function, key_equal, count,
> >>>     >> contains): Add usings.
> >>>     >>  (unordered_multiset<>::rehash, reserve): Add usings.
> >>>     >>  (unordered_multiset<>::merge): New.
> >>>     >>               *
> >>>     >> testsuite/23_containers/unordered_map/debug/merge1_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >> testsuite/23_containers/unordered_map/debug/merge2_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >> testsuite/23_containers/unordered_map/debug/merge3_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >> testsuite/23_containers/unordered_map/debug/merge4_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >>
> >>>     testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >>
> >>>     testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >>
> >>>     testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >>
> >>>     testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >>
> >>>     testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >>
> >>>     testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >>
> >>>     testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >>
> >>>     testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >> testsuite/23_containers/unordered_set/debug/merge1_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >> testsuite/23_containers/unordered_set/debug/merge2_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >> testsuite/23_containers/unordered_set/debug/merge3_neg.cc:
> >>>     New test.
> >>>     >>               *
> >>>     >> testsuite/23_containers/unordered_set/debug/merge4_neg.cc:
> >>>     New test.
> >>>     >>               * testsuite/util/testsuite_abi.h:
> >>>     [_GLIBCXX_DEBUG] Use
> >>>     >> normal unordered container implementation.
> >>>     >>
> >>>     >> Tested under Linux x86_64.
> >>>     >>
> >>>     >> Ok to commit ?
> >>>     > Yes, thanks. But ...
> >>>     >
> >>>     > This looks like an improvement over what we have now, but not 100%
> >>>     > correct. The merge functions can exit via exception (if any hash
> >>>     > function or equality predicate throws), and if that happens
> >>>     the safe
> >>>     > iterators will not get invalidated. I think we need to call
> >>>     > _Base::merge in a try-block, and do the iterator invalidation
> >>>     whether
> >>>     > we return normally or via an exception.
> >>>     >
> >>>     > Something like:
> >>>     >
> >>>     >    template<typename _H2, typename _P2>
> >>>     >      void
> >>>     >      merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
> >>>     >      {
> >>>     >        struct _Guard
> >>>     >        {
> >>>     >          _Guard(unordered_set& __source) noexcept
> >>>     >          : _M_source(__source), _M_size(__source.size())
> >>>     >          { }
> >>>     >
> >>>     >          ~_Guard()
> >>>     >          {
> >>>     >            const size_type __size = _M_source.size();
> >>>     >            if (__size != _M_size)
> >>>     >              {
> >>>     >                if (__size == 0)
> >>>     >                  _M_source._M_invalidate_all();
> >>>     >                else
> >>>     >                  {
> >>>     >                    auto __pred = [&__source](auto __it)
> >>>     >                                  { return
> >>>     __source.count(*__it) == 0; };
> >>>     > __source._M_invalidate_if(__pred);
> >>>     > __source._M_invalidate_local_if(__pred);
> >>>     >                  }
> >>>     >              }
> >>>     >          }
> >>>     >
> >>>     >          _Guard(const _Guard&) = delete;
> >>>     >
> >>>     >          unordered_set& _M_source;
> >>>     >          const size_type _M_size;
> >>>     >        };
> >>>     >        _Guard __guard(__source);
> >>>     >        _Base::merge(__source._M_base());
> >>>     >      }
> >>>     >
> >>>
> >>
> >
>


-- 
H.J.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-10  0:05             ` H.J. Lu
@ 2021-11-10  5:44               ` François Dumont
  2021-11-10  7:26                 ` Jonathan Wakely
  0 siblings, 1 reply; 21+ messages in thread
From: François Dumont @ 2021-11-10  5:44 UTC (permalink / raw)
  To: H.J. Lu; +Cc: Jonathan Wakely, Jonathan Wakely, libstdc++, gcc-patches

I can't see any clue on how my commit can have had an impact on below code.

I don't think libstdc++ hash table has any relation with gcc hash table.

Still, I'm rebuilding gcc at my revision to confirm.

On 10/11/21 1:05 am, H.J. Lu wrote:
> On Mon, Nov 8, 2021 at 1:37 PM François Dumont via Gcc-patches
> <gcc-patches@gcc.gnu.org> wrote:
>> Yet another version this time with only 1 guard implementation. The
>> predicate to invalidate the safe iterators has been externalized.
>>
>> Ok to commit ?
>>
> This may have broken GCC bootstrap on Linux/x86-64:
>
> https://gcc.gnu.org/pipermail/gcc-regression/2021-November/075734.html
>
> In file included from ../../src-master/gcc/sanopt.c:22:
> In member function ‘hash_table<Descriptor, Lazy,
> Allocator>::value_type* hash_table<Descriptor, Lazy,
> Allocator>::alloc_entries(size_t) const [with Descriptor =
> hash_map<tree_node*, auto_vec<gimple*> >::hash_entry; bool Lazy =
> false; Allocator = xcallocator]’,
>      inlined from ‘void hash_table<Descriptor, Lazy,
> Allocator>::expand() [with Descriptor = hash_map<tree_node*,
> auto_vec<gimple*> >::hash_entry; bool Lazy = false; Allocator =
> xcallocator]’ at ../../src-master/gcc/hash-table.h:802:40:
> ../../src-master/gcc/system.h:784:34: error: section type conflict
> with ‘void hash_table<Descriptor, Lazy, Allocator>::expand() [with
> Descriptor = hash_map<tree_node*, auto_vec<gimple*> >::hash_entry;
> bool Lazy = false; Allocator = xcallocator]’
>    784 |    ((void)(!(EXPR) ? fancy_abort (__FILE__, __LINE__,
> __FUNCTION__), 0 : 0))
>        |                      ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> ../../src-master/gcc/hash-table.h:715:3: note: in expansion of macro
> ‘gcc_assert’
>    715 |   gcc_assert (nentries != NULL);
>        |   ^~~~~~~~~~
> In file included from ../../src-master/gcc/coretypes.h:482,
>                   from ../../src-master/gcc/sanopt.c:23:
> ../../src-master/gcc/hash-table.h: In member function ‘void
> hash_table<Descriptor, Lazy, Allocator>::expand() [with Descriptor =
> hash_map<tree_node*, auto_vec<gimple*> >::hash_entry; bool Lazy =
> false; Allocator = xcallocator]’:
> ../../src-master/gcc/hash-table.h:779:1: note: ‘void
> hash_table<Descriptor, Lazy, Allocator>::expand() [with Descriptor =
> hash_map<tree_node*, auto_vec<gimple*> >::hash_entry; bool Lazy =
> false; Allocator = xcallocator]’ was declared here
>    779 | hash_table<Descriptor, Lazy, Allocator>::expand ()
>        | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
>> On 06/11/21 2:51 pm, François Dumont wrote:
>>> You were right to delay your reply. Here is a new version with less
>>> code duplication and a bug fix in the new _UContMergeGuard where we
>>> were using it->second rather than it->first to get the key.
>>>
>>> Note also that the change to _M_merge_multi implementation is also
>>> important because otherwise we might be trying to extract a key from a
>>> destructed node.
>>>
>>>      libstdc++: [_GLIBCXX_DEBUG] Implement unordered container merge
>>>
>>>      The _GLIBCXX_DEBUG unordered containers need a dedicated merge
>>> implementation
>>>      so that any existing iterator on the transfered nodes is properly
>>> invalidated.
>>>
>>>      Add typedef/using declaration for everything used as-is from
>>> normal implementation.
>>>
>>>      libstdc++-v3/ChangeLog:
>>>
>>>              * include/bits/hashtable_policy.h (__distance_fw): Replace
>>> class keyword with
>>>              typename.
>>>              * include/bits/hashtable.h
>>> (_Hashtable<>::_M_merge_unique): Remove noexcept
>>>              qualification. Use const_iterator for node
>>> extraction/reinsert.
>>>              (_Hashtable<>::_M_merge_multi): Likewise. Compute new hash
>>> code before extract.
>>>              * include/debug/safe_container.h (_Safe_container<>): Make
>>> all methods
>>>              protected.
>>>              * include/debug/safe_unordered_container.h
>>> (_Safe_unordered_container<>::_UContMergeGuard<_ExtractKey, _Source>):
>>> New.
>>> (_Safe_unordered_container<>::_S_uc_guard<_ExtractKey, _Source>): New.
>>> (_Safe_unordered_container<>::_UMContMergeGuard<_ExtractKey,
>>> _Source>): New.
>>> (_Safe_unordered_container<>::_S_umc_guard<_ExtractKey, _Source>): New.
>>> (_Safe_unordered_container<>::_M_invalide_all): Make public.
>>>              (_Safe_unordered_container<>::_M_invalide_if): Likewise.
>>> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>>>              * include/debug/unordered_map
>>>              (unordered_map<>::mapped_type, pointer, const_pointer):
>>> New typedef.
>>>              (unordered_map<>::reference, const_reference,
>>> difference_type): New typedef.
>>>              (unordered_map<>::get_allocator, empty, size, max_size):
>>> Add usings.
>>>              (unordered_map<>::bucket_count, max_bucket_count, bucket):
>>> Add usings.
>>>              (unordered_map<>::hash_function, key_equal, count,
>>> contains): Add usings.
>>>              (unordered_map<>::operator[], at, rehash, reserve): Add
>>> usings.
>>>              (unordered_map<>::merge): New.
>>>              (unordered_multimap<>::mapped_type, pointer,
>>> const_pointer): New typedef.
>>>              (unordered_multimap<>::reference, const_reference,
>>> difference_type): New typedef.
>>>              (unordered_multimap<>::get_allocator, empty, size,
>>> max_size): Add usings.
>>>              (unordered_multimap<>::bucket_count, max_bucket_count,
>>> bucket): Add usings.
>>>              (unordered_multimap<>::hash_function, key_equal, count,
>>> contains): Add usings.
>>>              (unordered_multimap<>::rehash, reserve): Add usings.
>>>              (unordered_multimap<>::merge): New.
>>>              * include/debug/unordered_set
>>>              (unordered_set<>::mapped_type, pointer, const_pointer):
>>> New typedef.
>>>              (unordered_set<>::reference, const_reference,
>>> difference_type): New typedef.
>>>              (unordered_set<>::get_allocator, empty, size, max_size):
>>> Add usings.
>>>              (unordered_set<>::bucket_count, max_bucket_count, bucket):
>>> Add usings.
>>>              (unordered_set<>::hash_function, key_equal, count,
>>> contains): Add usings.
>>>              (unordered_set<>::rehash, reserve): Add usings.
>>>              (unordered_set<>::merge): New.
>>>              (unordered_multiset<>::mapped_type, pointer,
>>> const_pointer): New typedef.
>>>              (unordered_multiset<>::reference, const_reference,
>>> difference_type): New typedef.
>>>              (unordered_multiset<>::get_allocator, empty, size,
>>> max_size): Add usings.
>>>              (unordered_multiset<>::bucket_count, max_bucket_count,
>>> bucket): Add usings.
>>>              (unordered_multiset<>::hash_function, key_equal, count,
>>> contains): Add usings.
>>>              (unordered_multiset<>::rehash, reserve): Add usings.
>>>              (unordered_multiset<>::merge): New.
>>>              *
>>> testsuite/23_containers/unordered_map/debug/merge1_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_map/debug/merge2_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_map/debug/merge3_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_map/debug/merge4_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_set/debug/merge1_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_set/debug/merge2_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_set/debug/merge3_neg.cc: New test.
>>>              *
>>> testsuite/23_containers/unordered_set/debug/merge4_neg.cc: New test.
>>>              * testsuite/util/testsuite_abi.h: [_GLIBCXX_DEBUG] Use
>>> normal unordered container implementation.
>>>
>>> Tested under Linux x86_64.
>>>
>>> Ok to commit ?
>>>
>>> François
>>>
>>> On 25/10/21 8:08 pm, François Dumont wrote:
>>>> New patch with the proposed workaround below.
>>>>
>>>> I also slightly change the _M_merge_multi implementation so that if
>>>> the new hash code computation raise an exception the node is simply
>>>> not extracted rather than extracted and then released. This way, if
>>>> it takes place on the 1st moved node the _GLIBCXX_DEBUG mode won't
>>>> try to invalidate anything because the source size won't have changed.
>>>>
>>>> Ok to commit ?
>>>>
>>>> François
>>>>
>>>>
>>>> On 16/10/21 4:52 pm, Jonathan Wakely wrote:
>>>>>
>>>>> On Sat, 16 Oct 2021, 14:49 François Dumont via Libstdc++,
>>>>> <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>>>>>
>>>>>      Hi
>>>>>
>>>>>           Here is the new proposal. My only concern is that we are
>>>>>      also using
>>>>>      hash or equal_to functors in the guard destructor.
>>>>>
>>>>>
>>>>>
>>>>> Can we catch any exception there, invalidate all iterators, and not
>>>>> rethrow the exception?
>>>>>
>>>>>
>>>>>           I am going to enhance merge normal implementation to make
>>>>>      use of
>>>>>      the cached hash code when hash functors are the same between the
>>>>>      source
>>>>>      and destination of nodes. Maybe I'll be able to make use of it
>>>>>      in Debug
>>>>>      implementation too.
>>>>>
>>>>>      François
>>>>>
>>>>>
>>>>>      On 14/10/21 10:23 am, Jonathan Wakely wrote:
>>>>>      > On Wed, 13 Oct 2021 at 18:10, François Dumont via Libstdc++
>>>>>      > <libstdc++@gcc.gnu.org <mailto:libstdc%2B%2B@gcc.gnu.org>> wrote:
>>>>>      >> Hi
>>>>>      >>
>>>>>      >>       libstdc++: [_GLIBCXX_DEBUG] Implement unordered
>>>>>      container merge
>>>>>      >>
>>>>>      >>       The _GLIBCXX_DEBUG unordered containers need a
>>>>>      dedicated merge
>>>>>      >> implementation
>>>>>      >>       so that any existing iterator on the transfered nodes
>>>>>      is properly
>>>>>      >> invalidated.
>>>>>      >>
>>>>>      >>       Add typedef/using declaration for everything used as-is
>>>>>      from normal
>>>>>      >> implementation.
>>>>>      >>
>>>>>      >>       libstdc++-v3/ChangeLog:
>>>>>      >>
>>>>>      >>               * include/debug/safe_container.h
>>>>>      (_Safe_container<>): Make
>>>>>      >> all methods
>>>>>      >>               protected.
>>>>>      >>               * include/debug/safe_unordered_container.h
>>>>>      >>  (_Safe_unordered_container<>::_M_invalide_all): Make public.
>>>>>      >>  (_Safe_unordered_container<>::_M_invalide_if): Likewise.
>>>>>      >> (_Safe_unordered_container<>::_M_invalide_local_if): Likewise.
>>>>>      >>               * include/debug/unordered_map
>>>>>      >>  (unordered_map<>::mapped_type, pointer, const_pointer): New
>>>>>      >> typedef.
>>>>>      >>  (unordered_map<>::reference, const_reference,
>>>>>      >> difference_type): New typedef.
>>>>>      >>  (unordered_map<>::get_allocator, empty, size, max_size):
>>>>>      >> Add usings.
>>>>>      >>  (unordered_map<>::bucket_count, max_bucket_count, bucket):
>>>>>      >> Add usings.
>>>>>      >>  (unordered_map<>::hash_function, key_equal, count,
>>>>>      >> contains): Add usings.
>>>>>      >>  (unordered_map<>::operator[], at, rehash, reserve): Add usings.
>>>>>      >>               (unordered_map<>::merge): New.
>>>>>      >>  (unordered_multimap<>::mapped_type, pointer,
>>>>>      >> const_pointer): New typedef.
>>>>>      >>  (unordered_multimap<>::reference, const_reference,
>>>>>      >> difference_type): New typedef.
>>>>>      >>  (unordered_multimap<>::get_allocator, empty, size,
>>>>>      >> max_size): Add usings.
>>>>>      >>  (unordered_multimap<>::bucket_count, max_bucket_count,
>>>>>      >> bucket): Add usings.
>>>>>      >>  (unordered_multimap<>::hash_function, key_equal, count,
>>>>>      >> contains): Add usings.
>>>>>      >>  (unordered_multimap<>::rehash, reserve): Add usings.
>>>>>      >>  (unordered_multimap<>::merge): New.
>>>>>      >>               * include/debug/unordered_set
>>>>>      >>  (unordered_set<>::mapped_type, pointer, const_pointer): New
>>>>>      >> typedef.
>>>>>      >>  (unordered_set<>::reference, const_reference,
>>>>>      >> difference_type): New typedef.
>>>>>      >>  (unordered_set<>::get_allocator, empty, size, max_size):
>>>>>      >> Add usings.
>>>>>      >>  (unordered_set<>::bucket_count, max_bucket_count, bucket):
>>>>>      >> Add usings.
>>>>>      >>  (unordered_set<>::hash_function, key_equal, count,
>>>>>      >> contains): Add usings.
>>>>>      >>               (unordered_set<>::rehash, reserve): Add usings.
>>>>>      >>               (unordered_set<>::merge): New.
>>>>>      >>  (unordered_multiset<>::mapped_type, pointer,
>>>>>      >> const_pointer): New typedef.
>>>>>      >>  (unordered_multiset<>::reference, const_reference,
>>>>>      >> difference_type): New typedef.
>>>>>      >>  (unordered_multiset<>::get_allocator, empty, size,
>>>>>      >> max_size): Add usings.
>>>>>      >>  (unordered_multiset<>::bucket_count, max_bucket_count,
>>>>>      >> bucket): Add usings.
>>>>>      >>  (unordered_multiset<>::hash_function, key_equal, count,
>>>>>      >> contains): Add usings.
>>>>>      >>  (unordered_multiset<>::rehash, reserve): Add usings.
>>>>>      >>  (unordered_multiset<>::merge): New.
>>>>>      >>               *
>>>>>      >> testsuite/23_containers/unordered_map/debug/merge1_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >> testsuite/23_containers/unordered_map/debug/merge2_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >> testsuite/23_containers/unordered_map/debug/merge3_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >> testsuite/23_containers/unordered_map/debug/merge4_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >>
>>>>>      testsuite/23_containers/unordered_multimap/debug/merge1_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >>
>>>>>      testsuite/23_containers/unordered_multimap/debug/merge2_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >>
>>>>>      testsuite/23_containers/unordered_multimap/debug/merge3_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >>
>>>>>      testsuite/23_containers/unordered_multimap/debug/merge4_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >>
>>>>>      testsuite/23_containers/unordered_multiset/debug/merge1_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >>
>>>>>      testsuite/23_containers/unordered_multiset/debug/merge2_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >>
>>>>>      testsuite/23_containers/unordered_multiset/debug/merge3_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >>
>>>>>      testsuite/23_containers/unordered_multiset/debug/merge4_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >> testsuite/23_containers/unordered_set/debug/merge1_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >> testsuite/23_containers/unordered_set/debug/merge2_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >> testsuite/23_containers/unordered_set/debug/merge3_neg.cc:
>>>>>      New test.
>>>>>      >>               *
>>>>>      >> testsuite/23_containers/unordered_set/debug/merge4_neg.cc:
>>>>>      New test.
>>>>>      >>               * testsuite/util/testsuite_abi.h:
>>>>>      [_GLIBCXX_DEBUG] Use
>>>>>      >> normal unordered container implementation.
>>>>>      >>
>>>>>      >> Tested under Linux x86_64.
>>>>>      >>
>>>>>      >> Ok to commit ?
>>>>>      > Yes, thanks. But ...
>>>>>      >
>>>>>      > This looks like an improvement over what we have now, but not 100%
>>>>>      > correct. The merge functions can exit via exception (if any hash
>>>>>      > function or equality predicate throws), and if that happens
>>>>>      the safe
>>>>>      > iterators will not get invalidated. I think we need to call
>>>>>      > _Base::merge in a try-block, and do the iterator invalidation
>>>>>      whether
>>>>>      > we return normally or via an exception.
>>>>>      >
>>>>>      > Something like:
>>>>>      >
>>>>>      >    template<typename _H2, typename _P2>
>>>>>      >      void
>>>>>      >      merge(unordered_set<_Value, _H2, _P2, _Alloc>& __source)
>>>>>      >      {
>>>>>      >        struct _Guard
>>>>>      >        {
>>>>>      >          _Guard(unordered_set& __source) noexcept
>>>>>      >          : _M_source(__source), _M_size(__source.size())
>>>>>      >          { }
>>>>>      >
>>>>>      >          ~_Guard()
>>>>>      >          {
>>>>>      >            const size_type __size = _M_source.size();
>>>>>      >            if (__size != _M_size)
>>>>>      >              {
>>>>>      >                if (__size == 0)
>>>>>      >                  _M_source._M_invalidate_all();
>>>>>      >                else
>>>>>      >                  {
>>>>>      >                    auto __pred = [&__source](auto __it)
>>>>>      >                                  { return
>>>>>      __source.count(*__it) == 0; };
>>>>>      > __source._M_invalidate_if(__pred);
>>>>>      > __source._M_invalidate_local_if(__pred);
>>>>>      >                  }
>>>>>      >              }
>>>>>      >          }
>>>>>      >
>>>>>      >          _Guard(const _Guard&) = delete;
>>>>>      >
>>>>>      >          unordered_set& _M_source;
>>>>>      >          const size_type _M_size;
>>>>>      >        };
>>>>>      >        _Guard __guard(__source);
>>>>>      >        _Base::merge(__source._M_base());
>>>>>      >      }
>>>>>      >
>>>>>
>


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-09 16:25             ` Jonathan Wakely
@ 2021-11-10  5:47               ` François Dumont
  2021-11-10  9:38                 ` Jonathan Wakely
  2021-11-10 11:55               ` Jonathan Wakely
  1 sibling, 1 reply; 21+ messages in thread
From: François Dumont @ 2021-11-10  5:47 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Jonathan Wakely, libstdc++, gcc-patches

On 09/11/21 5:25 pm, Jonathan Wakely wrote:
>
>
> On Mon, 8 Nov 2021 at 21:36, François Dumont <frs.dumont@gmail.com 
> <mailto:frs.dumont@gmail.com>> wrote:
>
>     Yet another version this time with only 1 guard implementation.
>     The predicate to invalidate the safe iterators has been externalized.
>
>     Ok to commit ?
>
>
> I like this version a lot - thanks for persisting with it.
>
> OK to commit, thanks.
>
>
> As an aside ...
>
> --- a/libstdc++-v3/testsuite/util/testsuite_abi.h
> +++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
> @@ -24,7 +24,11 @@
>  #include <locale>
>  #if __cplusplus >= 201103L
>  # include <unordered_map>
> +# ifdef _GLIBCXX_DEBUG
> +namespace unord = std::_GLIBCXX_STD_C;
> +# else
>  namespace unord = std;
> +# endif
>  #else
>  # include <tr1/unordered_map>
>  namespace unord = std::tr1;
>
>
> Several times I've been annoyed by the fact that we don't have a way 
> to refer to std::_GLIBCXX_STD_C::vector etc. that is always valid, in 
> normal mode and debug mode.
>
> Maybe we should add:
>
> namespace std { namespace _GLIBCXX_STD_C = ::std; }
>
> That way we can refer to std::_GLIBCXX_STD_C::foo in normal mode, and 
> it will mean the same thing as in debug mode. So we don't need to use 
> #if conditions like this.
>
>
Good idea, I'll prepare it.

François


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-10  5:44               ` François Dumont
@ 2021-11-10  7:26                 ` Jonathan Wakely
  0 siblings, 0 replies; 21+ messages in thread
From: Jonathan Wakely @ 2021-11-10  7:26 UTC (permalink / raw)
  To: François Dumont; +Cc: H.J. Lu, Jonathan Wakely, libstdc++, gcc-patches

On Wed, 10 Nov 2021, 05:45 François Dumont, <frs.dumont@gmail.com> wrote:

> I can't see any clue on how my commit can have had an impact on below code.
>


Agreed.


> I don't think libstdc++ hash table has any relation with gcc hash table.
>

Correct, it's totally unrelated. And "section type conflict" can't be
caused by the library anyway.




> Still, I'm rebuilding gcc at my revision to confirm.
>
> On 10/11/21 1:05 am, H.J. Lu wrote:
> > On Mon, Nov 8, 2021 at 1:37 PM François Dumont via Gcc-patches
> > <gcc-patches@gcc.gnu.org> wrote:
> >> Yet another version this time with only 1 guard implementation. The
> >> predicate to invalidate the safe iterators has been externalized.
> >>
> >> Ok to commit ?
> >>
> > This may have broken GCC bootstrap on Linux/x86-64:
> >
> > https://gcc.gnu.org/pipermail/gcc-regression/2021-November/075734.html
> >
> > In file included from ../../src-master/gcc/sanopt.c:22:
> > In member function ‘hash_table<Descriptor, Lazy,
> > Allocator>::value_type* hash_table<Descriptor, Lazy,
> > Allocator>::alloc_entries(size_t) const [with Descriptor =
> > hash_map<tree_node*, auto_vec<gimple*> >::hash_entry; bool Lazy =
> > false; Allocator = xcallocator]’,
> >      inlined from ‘void hash_table<Descriptor, Lazy,
> > Allocator>::expand() [with Descriptor = hash_map<tree_node*,
> > auto_vec<gimple*> >::hash_entry; bool Lazy = false; Allocator =
> > xcallocator]’ at ../../src-master/gcc/hash-table.h:802:40:
> > ../../src-master/gcc/system.h:784:34: error: section type conflict
> > with ‘void hash_table<Descriptor, Lazy, Allocator>::expand() [with
> > Descriptor = hash_map<tree_node*, auto_vec<gimple*> >::hash_entry;
> > bool Lazy = false; Allocator = xcallocator]’
> >    784 |    ((void)(!(EXPR) ? fancy_abort (__FILE__, __LINE__,
> > __FUNCTION__), 0 : 0))
> >        |
> ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > ../../src-master/gcc/hash-table.h:715:3: note: in expansion of macro
> > ‘gcc_assert’
> >    715 |   gcc_assert (nentries != NULL);
> >        |   ^~~~~~~~~~
> > In file included from ../../src-master/gcc/coretypes.h:482,
> >                   from ../../src-master/gcc/sanopt.c:23:
> > ../../src-master/gcc/hash-table.h: In member function ‘void
> > hash_table<Descriptor, Lazy, Allocator>::expand() [with Descriptor =
> > hash_map<tree_node*, auto_vec<gimple*> >::hash_entry; bool Lazy =
> > false; Allocator = xcallocator]’:
> > ../../src-master/gcc/hash-table.h:779:1: note: ‘void
> > hash_table<Descriptor, Lazy, Allocator>::expand() [with Descriptor =
> > hash_map<tree_node*, auto_vec<gimple*> >::hash_entry; bool Lazy =
> > false; Allocator = xcallocator]’ was declared here
> >    779 | hash_table<Descriptor, Lazy, Allocator>::expand ()
> >        | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-10  5:47               ` François Dumont
@ 2021-11-10  9:38                 ` Jonathan Wakely
  2021-11-15 18:16                   ` François Dumont
  0 siblings, 1 reply; 21+ messages in thread
From: Jonathan Wakely @ 2021-11-10  9:38 UTC (permalink / raw)
  To: François Dumont; +Cc: Jonathan Wakely, libstdc++, gcc-patches

On Wed, 10 Nov 2021 at 05:47, François Dumont <frs.dumont@gmail.com> wrote:

> On 09/11/21 5:25 pm, Jonathan Wakely wrote:
>
>
>
> On Mon, 8 Nov 2021 at 21:36, François Dumont <frs.dumont@gmail.com> wrote:
>
>> Yet another version this time with only 1 guard implementation. The
>> predicate to invalidate the safe iterators has been externalized.
>>
>> Ok to commit ?
>>
>
> I like this version a lot - thanks for persisting with it.
>
> OK to commit, thanks.
>
>
> As an aside ...
>
> --- a/libstdc++-v3/testsuite/util/testsuite_abi.h
> +++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
> @@ -24,7 +24,11 @@
>  #include <locale>
>  #if __cplusplus >= 201103L
>  # include <unordered_map>
> +# ifdef _GLIBCXX_DEBUG
> +namespace unord = std::_GLIBCXX_STD_C;
> +# else
>  namespace unord = std;
> +# endif
>  #else
>  # include <tr1/unordered_map>
>  namespace unord = std::tr1;
>
>
> Several times I've been annoyed by the fact that we don't have a way to
> refer to std::_GLIBCXX_STD_C::vector etc. that is always valid, in normal
> mode and debug mode.
>
> Maybe we should add:
>
> namespace std { namespace _GLIBCXX_STD_C = ::std; }
>
> That way we can refer to std::_GLIBCXX_STD_C::foo in normal mode, and it
> will mean the same thing as in debug mode. So we don't need to use #if
> conditions like this.
>
>
> Good idea, I'll prepare it.
>

Alternatively we could do this:

namespace std
{
namespace __cxx1998 { }
#ifdef _GLIBCXX_DEBUG
namespace __cont = __cxx1998;
#else
namespace __cont = ::std::
#endif
}

And then define this so it's always the same name:
#define _GLIBCXX_STD_C __cont

Then we can refer to std::_GLIBCXX_STD_C::vector in any context, and it
refers to the right thing. And we could also stop using the SHOUTING macro,
and just refer to std::__cont::vector instead.

We could also make this work as std::__cxx1998::vector, but maybe we should
move away from the "1998" name, because it doesn't make much sense for
forward_list and unordered_map which are not in C++98.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-09 16:25             ` Jonathan Wakely
  2021-11-10  5:47               ` François Dumont
@ 2021-11-10 11:55               ` Jonathan Wakely
  2021-11-11 20:41                 ` Jonathan Wakely
  1 sibling, 1 reply; 21+ messages in thread
From: Jonathan Wakely @ 2021-11-10 11:55 UTC (permalink / raw)
  To: François Dumont; +Cc: Jonathan Wakely, libstdc++, gcc-patches

On Tue, 9 Nov 2021 at 16:25, Jonathan Wakely <jwakely@redhat.com> wrote:

>
>
> On Mon, 8 Nov 2021 at 21:36, François Dumont <frs.dumont@gmail.com> wrote:
>
>> Yet another version this time with only 1 guard implementation. The
>> predicate to invalidate the safe iterators has been externalized.
>>
>> Ok to commit ?
>>
>
> I like this version a lot - thanks for persisting with it.
>
>

I'm seeing new failures with this:

make check RUNTESTFLAGS="conformance.exp=23_containers/*/invalidation/*
--target_board=unix/-D_GLIBCXX_DEBUG/-std=gnu++98"

FAIL: 23_containers/deque/debug/invalidation/1.cc (test for excess errors)
FAIL: 23_containers/list/debug/invalidation/1.cc (test for excess errors)
FAIL: 23_containers/map/debug/invalidation/1.cc (test for excess errors)
FAIL: 23_containers/multimap/debug/invalidation/1.cc (test for excess
errors)
FAIL: 23_containers/multiset/debug/invalidation/1.cc (test for excess
errors)
FAIL: 23_containers/set/debug/invalidation/1.cc (test for excess errors)
FAIL: 23_containers/vector/debug/invalidation/1.cc (test for excess errors)

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-10 11:55               ` Jonathan Wakely
@ 2021-11-11 20:41                 ` Jonathan Wakely
  2021-11-11 21:33                   ` François Dumont
  0 siblings, 1 reply; 21+ messages in thread
From: Jonathan Wakely @ 2021-11-11 20:41 UTC (permalink / raw)
  To: François Dumont; +Cc: libstdc++, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 1725 bytes --]

On Wed, 10 Nov 2021 at 11:55, Jonathan Wakely <jwakely@redhat.com> wrote:

>
>
> On Tue, 9 Nov 2021 at 16:25, Jonathan Wakely <jwakely@redhat.com> wrote:
>
>>
>>
>> On Mon, 8 Nov 2021 at 21:36, François Dumont <frs.dumont@gmail.com>
>> wrote:
>>
>>> Yet another version this time with only 1 guard implementation. The
>>> predicate to invalidate the safe iterators has been externalized.
>>>
>>> Ok to commit ?
>>>
>>
>> I like this version a lot - thanks for persisting with it.
>>
>>
>
> I'm seeing new failures with this:
>
> make check RUNTESTFLAGS="conformance.exp=23_containers/*/invalidation/*
> --target_board=unix/-D_GLIBCXX_DEBUG/-std=gnu++98"
>
> FAIL: 23_containers/deque/debug/invalidation/1.cc (test for excess errors)
> FAIL: 23_containers/list/debug/invalidation/1.cc (test for excess errors)
> FAIL: 23_containers/map/debug/invalidation/1.cc (test for excess errors)
> FAIL: 23_containers/multimap/debug/invalidation/1.cc (test for excess
> errors)
> FAIL: 23_containers/multiset/debug/invalidation/1.cc (test for excess
> errors)
> FAIL: 23_containers/set/debug/invalidation/1.cc (test for excess errors)
> FAIL: 23_containers/vector/debug/invalidation/1.cc (test for excess errors)
>

It's caused by:

--- a/libstdc++-v3/include/debug/safe_container.h
+++ b/libstdc++-v3/include/debug/safe_container.h
@@ -78,7 +78,6 @@ namespace __gnu_debug
      { }
#endif

-    public:
      // Copy assignment invalidate all iterators.
      _Safe_container&
      operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT


For C++98 mode that gets called explicitly by the user-provided copy
assignment operators in the derived class.

I'm testing the attached fix.

[-- Attachment #2: patch.txt --]
[-- Type: text/plain, Size: 7062 bytes --]

commit 7075abd518364b8d9767079e044baba86145cc08
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Thu Nov 11 20:23:48 2021

    libstdc++: Fix debug containers for C++98 mode
    
    Since r12-5072 made _Safe_container::operator=(const _Safe_container&)
    protected, the debug containers no longer compile in C++98 mode. They
    have user-provided copy assignment operators in C++98 mode, and they
    assign each base class in turn. The 'this->_M_safe() = __x' expressions
    fail, because calling a protected member function is only alowed via
    `this`. They could be fixed by using this->_Safe::operator=(__x) but a
    simpler solution is to just remove the user-provided assignment
    operators and let the compiler defined them (as in C++11 and later).
    
    The only change needed for that to work is to define the _Safe_vector
    copy assignment operator in C++98 mode, so that the implicit
    __gnu_debug::vector::operator= definition will call it, instead of
    calling _M_update_guaranteed_capacity() manually.
    
    libstdc++-v3/ChangeLog:
    
            * include/debug/deque (deque::operator=(const deque&)): Remove
            definition.
            * include/debug/list (list::operator=(const list&)): Likewise.
            * include/debug/map.h (map::operator=(const map&)): Likewise.
            * include/debug/multimap.h (multimap::operator=(const multimap&)):
            Likewise.
            * include/debug/multiset.h (multiset::operator=(const multiset&)):
            Likewise.
            * include/debug/set.h (set::operator=(const set&)): Likewise.
            * include/debug/string (basic_string::operator=(const basic_string&)):
            Likewise.
            * include/debug/vector (vector::operator=(const vector&)):
            Likewise.
            (_Safe_vector::operator=(const _Safe_vector&)): Define for
            C++98 as well.

diff --git a/libstdc++-v3/include/debug/deque b/libstdc++-v3/include/debug/deque
index 8e4811149d2..52778ba1617 100644
--- a/libstdc++-v3/include/debug/deque
+++ b/libstdc++-v3/include/debug/deque
@@ -156,15 +156,7 @@ namespace __debug
       deque(_Base_ref __x)
       : _Base(__x._M_ref) { }
 
-#if __cplusplus < 201103L
-      deque&
-      operator=(const deque& __x)
-      {
-	this->_M_safe() = __x;
-	_M_base() = __x;
-	return *this;
-      }
-#else
+#if __cplusplus >= 201103L
       deque&
       operator=(const deque&) = default;
 
diff --git a/libstdc++-v3/include/debug/list b/libstdc++-v3/include/debug/list
index de30edb19c2..f40ebc8521e 100644
--- a/libstdc++-v3/include/debug/list
+++ b/libstdc++-v3/include/debug/list
@@ -161,15 +161,7 @@ namespace __debug
       list(_Base_ref __x)
       : _Base(__x._M_ref) { }
 
-#if __cplusplus < 201103L
-      list&
-      operator=(const list& __x)
-      {
-	this->_M_safe() = __x;
-	_M_base() = __x;
-	return *this;
-      }
-#else
+#if __cplusplus >= 201103L
       list&
       operator=(const list&) = default;
 
diff --git a/libstdc++-v3/include/debug/map.h b/libstdc++-v3/include/debug/map.h
index 9e142cf7023..3883c546871 100644
--- a/libstdc++-v3/include/debug/map.h
+++ b/libstdc++-v3/include/debug/map.h
@@ -152,15 +152,7 @@ namespace __debug
 		__gnu_debug::__base(__last),
 		__comp, __a) { }
 
-#if __cplusplus < 201103L
-      map&
-      operator=(const map& __x)
-      {
-	this->_M_safe() = __x;
-	_M_base() = __x;
-	return *this;
-      }
-#else
+#if __cplusplus >= 201103L
       map&
       operator=(const map&) = default;
 
diff --git a/libstdc++-v3/include/debug/multimap.h b/libstdc++-v3/include/debug/multimap.h
index a05b8a8493e..073c1c39240 100644
--- a/libstdc++-v3/include/debug/multimap.h
+++ b/libstdc++-v3/include/debug/multimap.h
@@ -152,15 +152,7 @@ namespace __debug
       multimap(_Base_ref __x)
       : _Base(__x._M_ref) { }
 
-#if __cplusplus < 201103L
-      multimap&
-      operator=(const multimap& __x)
-      {
-	this->_M_safe() = __x;
-	_M_base() = __x;
-	return *this;
-      }
-#else
+#if __cplusplus >= 201103L
       multimap&
       operator=(const multimap&) = default;
 
diff --git a/libstdc++-v3/include/debug/multiset.h b/libstdc++-v3/include/debug/multiset.h
index a312ccf6f50..479411d9d06 100644
--- a/libstdc++-v3/include/debug/multiset.h
+++ b/libstdc++-v3/include/debug/multiset.h
@@ -152,15 +152,7 @@ namespace __debug
       multiset(_Base_ref __x)
       : _Base(__x._M_ref) { }
 
-#if __cplusplus < 201103L
-      multiset&
-      operator=(const multiset& __x)
-      {
-	this->_M_safe() = __x;
-	_M_base() = __x;
-	return *this;
-      }
-#else
+#if __cplusplus >= 201103L
       multiset&
       operator=(const multiset&) = default;
 
diff --git a/libstdc++-v3/include/debug/set.h b/libstdc++-v3/include/debug/set.h
index 01da942eb78..e35e5c1faae 100644
--- a/libstdc++-v3/include/debug/set.h
+++ b/libstdc++-v3/include/debug/set.h
@@ -150,15 +150,7 @@ namespace __debug
       set(_Base_ref __x)
       : _Base(__x._M_ref) { }
 
-#if __cplusplus < 201103L
-      set&
-      operator=(const set& __x)
-      {
-	this->_M_safe() = __x;
-	_M_base() = __x;
-	return *this;
-      }
-#else
+#if __cplusplus >= 201103L
       set&
       operator=(const set&) = default;
 
diff --git a/libstdc++-v3/include/debug/string b/libstdc++-v3/include/debug/string
index a8389528001..2209f88fd54 100644
--- a/libstdc++-v3/include/debug/string
+++ b/libstdc++-v3/include/debug/string
@@ -201,15 +201,7 @@ namespace __gnu_debug
 		  __glibcxx_check_valid_constructor_range(__begin, __end)),
 		__gnu_debug::__base(__end), __a) { }
 
-#if __cplusplus < 201103L
-      basic_string&
-      operator=(const basic_string& __str)
-      {
-	this->_M_safe() = __str;
-	_M_base() = __str;
-	return *this;
-      }
-#else
+#if __cplusplus >= 201103L
       basic_string&
       operator=(const basic_string&) = default;
 
diff --git a/libstdc++-v3/include/debug/vector b/libstdc++-v3/include/debug/vector
index 03fd9405cc9..b532a168e0e 100644
--- a/libstdc++-v3/include/debug/vector
+++ b/libstdc++-v3/include/debug/vector
@@ -71,18 +71,18 @@ namespace __gnu_debug
 	: _M_guaranteed_capacity(__n)
       { }
 
-#if __cplusplus >= 201103L
-      _Safe_vector(_Safe_vector&& __x) noexcept
-	: _Safe_vector()
-      { __x._M_guaranteed_capacity = 0; }
-
       _Safe_vector&
-      operator=(const _Safe_vector&) noexcept
+      operator=(const _Safe_vector&) _GLIBCXX_NOEXCEPT
       {
 	_M_update_guaranteed_capacity();
 	return *this;
       }
 
+#if __cplusplus >= 201103L
+      _Safe_vector(_Safe_vector&& __x) noexcept
+	: _Safe_vector()
+      { __x._M_guaranteed_capacity = 0; }
+
       _Safe_vector&
       operator=(_Safe_vector&& __x) noexcept
       {
@@ -234,16 +234,7 @@ namespace __debug
       vector(_Base_ref __x)
       : _Base(__x._M_ref) { }
 
-#if __cplusplus < 201103L
-      vector&
-      operator=(const vector& __x)
-      {
-	this->_M_safe() = __x;
-	_M_base() = __x;
-	this->_M_update_guaranteed_capacity();
-	return *this;
-      }
-#else
+#if __cplusplus >= 201103L
       vector&
       operator=(const vector&) = default;
 

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-11 20:41                 ` Jonathan Wakely
@ 2021-11-11 21:33                   ` François Dumont
  2021-11-11 22:01                     ` Jonathan Wakely
  0 siblings, 1 reply; 21+ messages in thread
From: François Dumont @ 2021-11-11 21:33 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: libstdc++, gcc-patches

On 11/11/21 9:41 pm, Jonathan Wakely wrote:
>
>
> On Wed, 10 Nov 2021 at 11:55, Jonathan Wakely <jwakely@redhat.com 
> <mailto:jwakely@redhat.com>> wrote:
>
>
>
>     On Tue, 9 Nov 2021 at 16:25, Jonathan Wakely <jwakely@redhat.com
>     <mailto:jwakely@redhat.com>> wrote:
>
>
>
>         On Mon, 8 Nov 2021 at 21:36, François Dumont
>         <frs.dumont@gmail.com <mailto:frs.dumont@gmail.com>> wrote:
>
>             Yet another version this time with only 1 guard
>             implementation. The predicate to invalidate the safe
>             iterators has been externalized.
>
>             Ok to commit ?
>
>
>         I like this version a lot - thanks for persisting with it.
>
>
>
>     I'm seeing new failures with this:
>
>     make check
>     RUNTESTFLAGS="conformance.exp=23_containers/*/invalidation/*
>     --target_board=unix/-D_GLIBCXX_DEBUG/-std=gnu++98"
>
>     FAIL: 23_containers/deque/debug/invalidation/1.cc (test for excess
>     errors)
>     FAIL: 23_containers/list/debug/invalidation/1.cc (test for excess
>     errors)
>     FAIL: 23_containers/map/debug/invalidation/1.cc (test for excess
>     errors)
>     FAIL: 23_containers/multimap/debug/invalidation/1.cc (test for
>     excess errors)
>     FAIL: 23_containers/multiset/debug/invalidation/1.cc (test for
>     excess errors)
>     FAIL: 23_containers/set/debug/invalidation/1.cc (test for excess
>     errors)
>     FAIL: 23_containers/vector/debug/invalidation/1.cc (test for
>     excess errors)
>
>
> It's caused by:
>
> --- a/libstdc++-v3/include/debug/safe_container.h
> +++ b/libstdc++-v3/include/debug/safe_container.h
> @@ -78,7 +78,6 @@namespace __gnu_debug
>       { }
> #endif
>
> -    public:
>       // Copy assignment invalidate all iterators.
>       _Safe_container&
>       operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT
>
>
> For C++98 mode that gets called explicitly by the user-provided copy 
> assignment operators in the derived class.
>
> I'm testing the attached fix.
>
I am also testing a patch but yours looks nicer so go ahead. I'll just 
complete it with some additional cleanup I did to suppress 
_Safe_container::_M_safe() and reduce usages of _M_base().

Thanks



^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-11 21:33                   ` François Dumont
@ 2021-11-11 22:01                     ` Jonathan Wakely
  0 siblings, 0 replies; 21+ messages in thread
From: Jonathan Wakely @ 2021-11-11 22:01 UTC (permalink / raw)
  To: François Dumont; +Cc: libstdc++, gcc-patches

On Thu, 11 Nov 2021 at 21:33, François Dumont  wrote:

> On 11/11/21 9:41 pm, Jonathan Wakely wrote:
>
>
>
> On Wed, 10 Nov 2021 at 11:55, Jonathan Wakely <jwakely@redhat.com> wrote:
>
>>
>>
>> On Tue, 9 Nov 2021 at 16:25, Jonathan Wakely <jwakely@redhat.com> wrote:
>>
>>>
>>>
>>> On Mon, 8 Nov 2021 at 21:36, François Dumont <frs.dumont@gmail.com>
>>> wrote:
>>>
>>>> Yet another version this time with only 1 guard implementation. The
>>>> predicate to invalidate the safe iterators has been externalized.
>>>>
>>>> Ok to commit ?
>>>>
>>>
>>> I like this version a lot - thanks for persisting with it.
>>>
>>>
>>
>> I'm seeing new failures with this:
>>
>> make check RUNTESTFLAGS="conformance.exp=23_containers/*/invalidation/*
>> --target_board=unix/-D_GLIBCXX_DEBUG/-std=gnu++98"
>>
>> FAIL: 23_containers/deque/debug/invalidation/1.cc (test for excess
>> errors)
>> FAIL: 23_containers/list/debug/invalidation/1.cc (test for excess errors)
>> FAIL: 23_containers/map/debug/invalidation/1.cc (test for excess errors)
>> FAIL: 23_containers/multimap/debug/invalidation/1.cc (test for excess
>> errors)
>> FAIL: 23_containers/multiset/debug/invalidation/1.cc (test for excess
>> errors)
>> FAIL: 23_containers/set/debug/invalidation/1.cc (test for excess errors)
>> FAIL: 23_containers/vector/debug/invalidation/1.cc (test for excess
>> errors)
>>
>
> It's caused by:
>
> --- a/libstdc++-v3/include/debug/safe_container.h
> +++ b/libstdc++-v3/include/debug/safe_container.h
> @@ -78,7 +78,6 @@ namespace __gnu_debug
>       { }
> #endif
>
> -    public:
>       // Copy assignment invalidate all iterators.
>       _Safe_container&
>       operator=(const _Safe_container&) _GLIBCXX_NOEXCEPT
>
>
> For C++98 mode that gets called explicitly by the user-provided copy
> assignment operators in the derived class.
>
> I'm testing the attached fix.
>
> I am also testing a patch but yours looks nicer so go ahead.
>

I've pushed it to trunk now.



> I'll just complete it with some additional cleanup I did to suppress
> _Safe_container::_M_safe() and reduce usages of _M_base().
>

Sounds good, thanks.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATH][_GLIBCXX_DEBUG] Fix unordered container merge
  2021-11-10  9:38                 ` Jonathan Wakely
@ 2021-11-15 18:16                   ` François Dumont
  0 siblings, 0 replies; 21+ messages in thread
From: François Dumont @ 2021-11-15 18:16 UTC (permalink / raw)
  To: Jonathan Wakely; +Cc: Jonathan Wakely, libstdc++

[-- Attachment #1: Type: text/plain, Size: 2816 bytes --]

On 10/11/21 10:38 am, Jonathan Wakely wrote:
>
>
> On Wed, 10 Nov 2021 at 05:47, François Dumont <frs.dumont@gmail.com 
> <mailto:frs.dumont@gmail.com>> wrote:
>
>     On 09/11/21 5:25 pm, Jonathan Wakely wrote:
>>
>>
>>     On Mon, 8 Nov 2021 at 21:36, François Dumont
>>     <frs.dumont@gmail.com <mailto:frs.dumont@gmail.com>> wrote:
>>
>>         Yet another version this time with only 1 guard
>>         implementation. The predicate to invalidate the safe
>>         iterators has been externalized.
>>
>>         Ok to commit ?
>>
>>
>>     I like this version a lot - thanks for persisting with it.
>>
>>     OK to commit, thanks.
>>
>>
>>     As an aside ...
>>
>>     --- a/libstdc++-v3/testsuite/util/testsuite_abi.h
>>     +++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
>>     @@ -24,7 +24,11 @@
>>      #include <locale>
>>      #if __cplusplus >= 201103L
>>      # include <unordered_map>
>>     +# ifdef _GLIBCXX_DEBUG
>>     +namespace unord = std::_GLIBCXX_STD_C;
>>     +# else
>>      namespace unord = std;
>>     +# endif
>>      #else
>>      # include <tr1/unordered_map>
>>      namespace unord = std::tr1;
>>
>>
>>     Several times I've been annoyed by the fact that we don't have a
>>     way to refer to std::_GLIBCXX_STD_C::vector etc. that is always
>>     valid, in normal mode and debug mode.
>>
>>     Maybe we should add:
>>
>>     namespace std { namespace _GLIBCXX_STD_C = ::std; }
>>
>>     That way we can refer to std::_GLIBCXX_STD_C::foo in normal mode,
>>     and it will mean the same thing as in debug mode. So we don't
>>     need to use #if conditions like this.
>>
>>
>     Good idea, I'll prepare it.
>
>
> Alternatively we could do this:
>
> namespace std
> {
> namespace __cxx1998 { }
> #ifdef _GLIBCXX_DEBUG
> namespace __cont = __cxx1998;
> #else
> namespace __cont = ::std::
> #endif
> }
>
> And then define this so it's always the same name:
> #define _GLIBCXX_STD_C __cont
>
> Then we can refer to std::_GLIBCXX_STD_C::vector in any context, and 
> it refers to the right thing. And we could also stop using the 
> SHOUTING macro, and just refer to std::__cont::vector instead.
>
> We could also make this work as std::__cxx1998::vector, but maybe we 
> should move away from the "1998" name, because it doesn't make much 
> sense for forward_list and unordered_map which are not in C++98.
>
Here is what I started.

I use '__normal' because when greping '__cont' in the lib it reported me 
a definition of it in boost_concept_check.h. But now that I took a 
closer look in this file I realize that it is just where you pick the 
idea of the '__cont' !

So, what do you prefer ? __cont or __normal ?

I was not considering to replace usage of _GLIBCXX_STD_C in the lib as 
long as there is no #ifdef _GLIBCXX_DEBUG. Do you prefer I do ?

François


[-- Attachment #2: normal_ns.patch --]
[-- Type: text/x-patch, Size: 2109 bytes --]

diff --git a/libstdc++-v3/include/bits/c++config b/libstdc++-v3/include/bits/c++config
index 4b7fa659300..becd83af4d9 100644
--- a/libstdc++-v3/include/bits/c++config
+++ b/libstdc++-v3/include/bits/c++config
@@ -245,6 +245,7 @@
     namespace __debug { }
     namespace __parallel { }
     namespace __cxx1998 { }
+    namespace __normal { }
 
     namespace __detail {
       namespace __variant { }				// C++17
@@ -384,6 +385,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 # endif
   }
 
+  namespace __normal = __cxx1998;
+
 _GLIBCXX_END_NAMESPACE_VERSION
 
   // Inline namespace for debug mode.
@@ -407,6 +410,11 @@ _GLIBCXX_END_NAMESPACE_VERSION
 #  warning currently using inlined namespace mode which may fail \
    without inlining due to lack of weak symbols
 # endif
+#else
+namespace std
+{
+  namespace __normal = ::std;
+}
 #endif
 
 // Macros for namespace scope. Either namespace std:: or the name
diff --git a/libstdc++-v3/testsuite/23_containers/forward_list/capacity/1.cc b/libstdc++-v3/testsuite/23_containers/forward_list/capacity/1.cc
index dc2a2be3867..399064ea074 100644
--- a/libstdc++-v3/testsuite/23_containers/forward_list/capacity/1.cc
+++ b/libstdc++-v3/testsuite/23_containers/forward_list/capacity/1.cc
@@ -35,12 +35,7 @@ test01()
   fld.resize(0);
   VERIFY(fld.empty() == true);
 
-#ifdef _GLIBCXX_DEBUG
-  using std::_GLIBCXX_STD_C::_Fwd_list_node;
-#else
-  using std::_Fwd_list_node;
-#endif
-
+  using std::__normal::_Fwd_list_node;
   std::allocator<_Fwd_list_node<double> > a;
   VERIFY( fld.max_size() == __gnu_test::max_size(a) );
 }
diff --git a/libstdc++-v3/testsuite/util/testsuite_abi.h b/libstdc++-v3/testsuite/util/testsuite_abi.h
index 4a0cf64f6fb..d0902db9193 100644
--- a/libstdc++-v3/testsuite/util/testsuite_abi.h
+++ b/libstdc++-v3/testsuite/util/testsuite_abi.h
@@ -24,11 +24,7 @@
 #include <locale>
 #if __cplusplus >= 201103L
 # include <unordered_map>
-# ifdef _GLIBCXX_DEBUG
-namespace unord = std::_GLIBCXX_STD_C;
-# else
-namespace unord = std;
-# endif
+namespace unord = std::__normal;
 #else
 # include <tr1/unordered_map>
 namespace unord = std::tr1;

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2021-11-15 18:16 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-13 17:10 [PATH][_GLIBCXX_DEBUG] Fix unordered container merge François Dumont
2021-10-14  8:23 ` Jonathan Wakely
2021-10-16 13:47   ` François Dumont
2021-10-16 14:52     ` Jonathan Wakely
2021-10-21 16:51       ` François Dumont
2021-10-21 16:55         ` Jonathan Wakely
2021-10-22  5:22           ` François Dumont
2021-10-25 18:08       ` François Dumont
2021-11-06 13:51         ` François Dumont
2021-11-08 21:36           ` François Dumont
2021-11-09 16:25             ` Jonathan Wakely
2021-11-10  5:47               ` François Dumont
2021-11-10  9:38                 ` Jonathan Wakely
2021-11-15 18:16                   ` François Dumont
2021-11-10 11:55               ` Jonathan Wakely
2021-11-11 20:41                 ` Jonathan Wakely
2021-11-11 21:33                   ` François Dumont
2021-11-11 22:01                     ` Jonathan Wakely
2021-11-10  0:05             ` H.J. Lu
2021-11-10  5:44               ` François Dumont
2021-11-10  7:26                 ` Jonathan Wakely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).