On Tue, 03 Feb 2026 at 09:17 -0500, Nathan Myers wrote:
Changes in v4:
* Revert #include-order churn, change to #ifdef on __glibcxx_*
  macros.
* Revert static_assert in comparison object that checked
  heterogeneous comparison, for failing 31440.cc (map<T*>::
  find(0)). A smarter check might work.
* Remove unnecessary #ifdefs on unobtrusive new functions.
* Simplify new-concept argument (remove unneeded &&).
* Weaken #ifdef on new concepts to just __cpp_concepts.
* Replace impls of various non-heterogeneous-key function
  templates with inline calls to the more general _Kt/*_tr
  version, avoiding code duplication.
* Remove spurious P2363 changes.
* Conform template parameter names to existing usage
  (s/_HetKey/_Kt/).
* Remove "P2077" commentary.
* Comment out redundant #includes in bits/stl_set.h and
  bits/stl_map.h
* Fill out ChangeLog with named objects added/changed.
* Make new tests actually run, not just compile.

Changes in v3:
* Implement permissive matching for het erase on {,multi}{set,map},
  remove now unused _M_erase_unique_tr, test.
* Adjust indent on erase_some.
* Remove copyright decls from tests.
* Use (defaulted) operator<=> in tree tests.
* Rename tests for content rather than paper number.
* Remove hashtable.h change meant for p2363.

Changes in v2:
* Remove excess remove_reference<> and remove_cxref<> in new concepts.
* Reorder terms in concept expressions to favor short-circuiting
 evaluation in common cases.
* Declare with "template <concept ..." so that users' types are named
 in error messages.

Remaining to do:
* Update debug headers.

Implement C++23 P2077R3 "Heterogeneous erasure overloads for
associative containers". Adds template overloads for members
erase and extract to address elements using an alternative key
type, such as string_view for a container of strings, without
need to construct an actual key object.

The new overloads enforce concept __heterogeneous_tree_key or
__heterogeneous_hash_key to verify the function objects provided
meet requirements, and that the key supplied is not an iterator
or the native key.


OK, thanks


libstdc++-v3/ChangeLog:
        PR libstdc++/117404
        * include/bits/version.def (associative_heterogeneous_erasure):
        Define.
        * include/bits/version.h: Regenerate.
        * include/std/map: Request new feature from version.h.
        * include/std/set: Same.
        * include/std/unordered_map: Same.
        * include/std/unordered_set: Same.
        * include/bits/stl_map.h (extract, erase): Define overloads.
        * include/bits/stl_set.h: Same.
        * include/bits/stl_multimap.h: Same.
        * include/bits/stl_multiset.h: Same.
        * include/bits/unordered_map.h: Same, 2x.
        * include/bits/unordered_set.h: Same, 2x.
        * include/bits/stl_function.h (concepts __not_container_iterator,
        __heterogeneous_key): Define.
        * include/bits/hashtable.h (_M_find_before_node, _M_locate, extract):
        Delegate to more-general _tr version.
        (_M_find_before_node_tr, _M_locate_tr, _M_extract_tr, _M_erase_tr):
        Add new members to support a heterogeneous key argument.
        (_M_erase_some): Add new helper function.
        (concept __heterogeneous_hash_key): Define.
        * include/bits/stl_tree.h (_M_lower_bound_tr, _M_upper_bound_tr,
        _M_erase_tr, _M_extract_tr): Add new members to support a
        heterogeneous key argument.
        (concept __heterogeneous_tree_key): Define.
        * testsuite/23_containers/map/modifiers/hetero/erase.cc: New test.
        * testsuite/23_containers/multimap/modifiers/hetero/erase.cc: Same.
        * testsuite/23_containers/multiset/modifiers/hetero/erase.cc: Same.
        * testsuite/23_containers/set/modifiers/hetero/erase.cc: Same.
        * testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc: Same.
        * testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc:
        Same.
        * testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc:
        Same.
        * testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc: Same.
---
libstdc++-v3/include/bits/hashtable.h         | 241 +++++++++++-------
libstdc++-v3/include/bits/stl_function.h      |  12 +
libstdc++-v3/include/bits/stl_map.h           |  20 ++
libstdc++-v3/include/bits/stl_multimap.h      |  15 ++
libstdc++-v3/include/bits/stl_multiset.h      |  15 ++
libstdc++-v3/include/bits/stl_set.h           |  18 ++
libstdc++-v3/include/bits/stl_tree.h          |  75 +++++-
libstdc++-v3/include/bits/unordered_map.h     |  28 ++
libstdc++-v3/include/bits/unordered_set.h     |  28 ++
libstdc++-v3/include/bits/version.def         |   8 +
libstdc++-v3/include/bits/version.h           |  10 +
libstdc++-v3/include/std/map                  |   1 +
libstdc++-v3/include/std/set                  |   1 +
libstdc++-v3/include/std/unordered_map        |   1 +
libstdc++-v3/include/std/unordered_set        |   1 +
.../map/modifiers/hetero/erase.cc             |  95 +++++++
.../multimap/modifiers/hetero/erase.cc        |  95 +++++++
.../multiset/modifiers/hetero/erase.cc        |  88 +++++++
.../set/modifiers/hetero/erase.cc             |  88 +++++++
.../unordered_map/modifiers/hetero/erase.cc   |  76 ++++++
.../modifiers/hetero/erase.cc                 |  75 ++++++
.../modifiers/hetero/erase.cc                 |  73 ++++++
.../unordered_set/modifiers/hetero/erase.cc   |  73 ++++++
23 files changed, 1037 insertions(+), 100 deletions(-)
create mode 100644 
libstdc++-v3/testsuite/23_containers/map/modifiers/hetero/erase.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/multimap/modifiers/hetero/erase.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/multiset/modifiers/hetero/erase.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/set/modifiers/hetero/erase.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc
create mode 100644 
libstdc++-v3/testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc

diff --git a/libstdc++-v3/include/bits/hashtable.h 
b/libstdc++-v3/include/bits/hashtable.h
index c92b843bf0b..f7b8905d8ab 100644
--- a/libstdc++-v3/include/bits/hashtable.h
+++ b/libstdc++-v3/include/bits/hashtable.h
@@ -853,7 +853,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION

      // Find the node before the one matching the criteria.
      __node_base_ptr
-      _M_find_before_node(size_type, const key_type&, __hash_code) const;
+      _M_find_before_node(
+       size_type __bkt, const key_type& __k, __hash_code __code) const
+      { return _M_find_before_node_tr<key_type>(__bkt, __k, __code); }

      template<typename _Kt>
        __node_base_ptr
@@ -902,8 +904,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      // The _M_before pointer might point to _M_before_begin, so must not be
      // cast to __node_ptr, and it must not be used to modify *_M_before
      // except in non-const member functions, such as erase.
+
      __location_type
-      _M_locate(const key_type& __k) const;
+      _M_locate(const key_type& __k) const
+      { return _M_locate_tr<key_type>(__k); }
+
+      template <typename _Kt>
+       __location_type
+       _M_locate_tr(const _Kt& __k) const;

      __node_ptr
      _M_find_node(size_type __bkt, const key_type& __key,
@@ -1016,6 +1024,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      iterator
      _M_erase(size_type __bkt, __node_base_ptr __prev_n, __node_ptr __n);

+      size_type
+      _M_erase_some(size_type __bkt, __node_base_ptr __prev_n, __node_ptr __n);
+
      template<typename _InputIterator>
        void
        _M_insert_range_multi(_InputIterator __first, _InputIterator __last);
@@ -1163,6 +1174,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      size_type
      erase(const key_type& __k);

+      template <typename _Kt>
+       size_type
+       _M_erase_tr(const _Kt& __k);
+
      iterator
      erase(const_iterator, const_iterator);

@@ -1274,14 +1289,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      /// Extract a node.
      node_type
      extract(const _Key& __k)
-      {
-       node_type __nh;
-       __hash_code __code = this->_M_hash_code(__k);
-       std::size_t __bkt = _M_bucket_index(__code);
-       if (__node_base_ptr __prev_node = _M_find_before_node(__bkt, __k, 
__code))
-         __nh = _M_extract_node(__bkt, __prev_node);
-       return __nh;
-      }
+      { return _M_extract_tr<_Key>(__k); }
+
+      template <typename _Kt>
+       node_type
+       _M_extract_tr(const _Kt& __k)
+       {
+         node_type __nh;
+         __hash_code __code = this->_M_hash_code_tr(__k);
+         std::size_t __bkt = _M_bucket_index(__code);
+         if (__node_base_ptr __prev_node =
+             _M_find_before_node_tr(__bkt, __k, __code))
+           __nh = _M_extract_node(__bkt, __prev_node);
+         return __nh;
+       }

      /// Merge from another container of the same type.
      void
@@ -2194,35 +2215,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION

  // Find the node before the one whose key compares equal to k in the bucket
  // bkt. Return nullptr if no node is found.
-  template<typename _Key, typename _Value, typename _Alloc,
-          typename _ExtractKey, typename _Equal,
-          typename _Hash, typename _RangeHash, typename _Unused,
-          typename _RehashPolicy, typename _Traits>
-    auto
-    _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
-              _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
-    _M_find_before_node(size_type __bkt, const key_type& __k,
-                       __hash_code __code) const
-    -> __node_base_ptr
-    {
-      __node_base_ptr __prev_p = _M_buckets[__bkt];
-      if (!__prev_p)
-       return nullptr;
-
-      for (__node_ptr __p = static_cast<__node_ptr>(__prev_p->_M_nxt);;
-          __p = __p->_M_next())
-       {
-         if (this->_M_equals(__k, __code, *__p))
-           return __prev_p;
-
-         if (__builtin_expect (!__p->_M_nxt || 
_M_bucket_index(*__p->_M_next()) != __bkt, 0))
-           break;
-         __prev_p = __p;
-       }
-
-      return nullptr;
-    }
-
  template<typename _Key, typename _Value, typename _Alloc,
           typename _ExtractKey, typename _Equal,
           typename _Hash, typename _RangeHash, typename _Unused,
@@ -2245,7 +2237,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            if (this->_M_equals_tr(__k, __code, *__p))
              return __prev_p;

-           if (__builtin_expect (!__p->_M_nxt || 
_M_bucket_index(*__p->_M_next()) != __bkt, 0))
+           if (__builtin_expect (
+                 !__p->_M_nxt || _M_bucket_index(*__p->_M_next()) != __bkt, 0))
              break;
            __prev_p = __p;
          }
@@ -2257,37 +2250,38 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
           typename _ExtractKey, typename _Equal,
           typename _Hash, typename _RangeHash, typename _Unused,
           typename _RehashPolicy, typename _Traits>
-    inline auto
-    _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
-              _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
-    _M_locate(const key_type& __k) const
-    -> __location_type
-    {
-      __location_type __loc;
-      const auto __size = size();
+    template <typename _Kt>
+      inline auto
+      _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
+                _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
+      _M_locate_tr(const _Kt& __k) const
+       -> __location_type
+      {
+       __location_type __loc;
+       const auto __size = size();

-      if (__size <= __small_size_threshold())
-       {
-         __loc._M_before = pointer_traits<__node_base_ptr>::
-              pointer_to(const_cast<__node_base&>(_M_before_begin));
-         while (__loc._M_before->_M_nxt)
-           {
-             if (this->_M_key_equals(__k, *__loc._M_node()))
-               return __loc;
-             __loc._M_before = __loc._M_before->_M_nxt;
-           }
-         __loc._M_before = nullptr; // Didn't find it.
-       }
+       if (__size <= __small_size_threshold())
+         {
+           __loc._M_before = pointer_traits<__node_base_ptr>::
+                pointer_to(const_cast<__node_base&>(_M_before_begin));
+           while (__loc._M_before->_M_nxt)
+             {
+               if (this->_M_key_equals_tr(__k, *__loc._M_node()))
+                 return __loc;
+               __loc._M_before = __loc._M_before->_M_nxt;
+             }
+           __loc._M_before = nullptr; // Didn't find it.
+         }

-      __loc._M_hash_code = this->_M_hash_code(__k);
-      __loc._M_bucket_index = _M_bucket_index(__loc._M_hash_code);
+       __loc._M_hash_code = this->_M_hash_code_tr(__k);
+       __loc._M_bucket_index = _M_bucket_index(__loc._M_hash_code);

-      if (__size > __small_size_threshold())
-       __loc._M_before = _M_find_before_node(__loc._M_bucket_index, __k,
-                                             __loc._M_hash_code);
+       if (__size > __small_size_threshold())
+         __loc._M_before = _M_find_before_node_tr(
+           __loc._M_bucket_index, __k, __loc._M_hash_code);

-      return __loc;
-    }
+       return __loc;
+      }

  template<typename _Key, typename _Value, typename _Alloc,
           typename _ExtractKey, typename _Equal,
@@ -2598,6 +2592,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
+
  template<typename _Key, typename _Value, typename _Alloc,
           typename _ExtractKey, typename _Equal,
           typename _Hash, typename _RangeHash, typename _Unused,
@@ -2617,47 +2612,87 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      auto __bkt = __loc._M_bucket_index;
      if (__bkt == size_type(-1))
        __bkt = _M_bucket_index(*__n);
-
      if constexpr (__unique_keys::value)
        {
          _M_erase(__bkt, __prev_n, __n);
          return 1;
        }
      else
+       return _M_erase_some(__bkt, __prev_n, __n);
+    }
+
+  template<typename _Key, typename _Value, typename _Alloc,
+          typename _ExtractKey, typename _Equal,
+          typename _Hash, typename _RangeHash, typename _Unused,
+          typename _RehashPolicy, typename _Traits>
+    auto
+    _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
+              _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
+    _M_erase_some(size_type __bkt, __node_base_ptr __prev_n, __node_ptr __n)
+      -> size_type
+    {
+      // _GLIBCXX_RESOLVE_LIB_DEFECTS
+      // 526. Is it undefined if a function in the standard changes
+      // in parameters?
+      // We use one loop to find all matching nodes and another to
+      // deallocate them so that the key stays valid during the first loop.
+      // It might be invalidated indirectly when destroying nodes.
+      __node_ptr __n_last = __n->_M_next();
+      while (__n_last && this->_M_node_equals(*__n, *__n_last))
+       __n_last = __n_last->_M_next();
+
+      std::size_t __n_last_bkt
+       = __n_last ? _M_bucket_index(*__n_last) : __bkt;
+
+      // Deallocate nodes.
+      size_type __result = 0;
+      do
        {
-         // _GLIBCXX_RESOLVE_LIB_DEFECTS
-         // 526. Is it undefined if a function in the standard changes
-         // in parameters?
-         // We use one loop to find all matching nodes and another to
-         // deallocate them so that the key stays valid during the first loop.
-         // It might be invalidated indirectly when destroying nodes.
-         __node_ptr __n_last = __n->_M_next();
-         while (__n_last && this->_M_node_equals(*__n, *__n_last))
-           __n_last = __n_last->_M_next();
-
-         std::size_t __n_last_bkt
-           = __n_last ? _M_bucket_index(*__n_last) : __bkt;
-
-         // Deallocate nodes.
-         size_type __result = 0;
-         do
-           {
-             __node_ptr __p = __n->_M_next();
-             this->_M_deallocate_node(__n);
-             __n = __p;
-             ++__result;
-           }
-         while (__n != __n_last);
-
-         _M_element_count -= __result;
-         if (__prev_n == _M_buckets[__bkt])
-           _M_remove_bucket_begin(__bkt, __n_last, __n_last_bkt);
-         else if (__n_last_bkt != __bkt)
-           _M_buckets[__n_last_bkt] = __prev_n;
-         __prev_n->_M_nxt = __n_last;
-         return __result;
+         __node_ptr __p = __n->_M_next();
+         this->_M_deallocate_node(__n);
+         __n = __p;
+         ++__result;
        }
+      while (__n != __n_last);
+
+      _M_element_count -= __result;
+      if (__prev_n == _M_buckets[__bkt])
+       _M_remove_bucket_begin(__bkt, __n_last, __n_last_bkt);
+      else if (__n_last_bkt != __bkt)
+       _M_buckets[__n_last_bkt] = __prev_n;
+      __prev_n->_M_nxt = __n_last;
+      return __result;
    }
+
+  template<typename _Key, typename _Value, typename _Alloc,
+          typename _ExtractKey, typename _Equal,
+          typename _Hash, typename _RangeHash, typename _Unused,
+          typename _RehashPolicy, typename _Traits>
+    template <typename _Kt>
+      auto
+      _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
+                _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
+      _M_erase_tr(const _Kt& __k)
+       -> size_type
+      {
+       auto __loc = _M_locate_tr(__k);
+       if (!__loc)
+         return 0;
+
+       __node_base_ptr __prev_n = __loc._M_before;
+       __node_ptr __n = __loc._M_node();
+       auto __bkt = __loc._M_bucket_index;
+       if (__bkt == size_type(-1))
+         __bkt = _M_bucket_index(*__n);
+       if constexpr (__unique_keys::value)
+         {
+           _M_erase(__bkt, __prev_n, __n);
+           return 1;
+         }
+       else
+         return _M_erase_some(__bkt, __prev_n, __n);
+      }
+
#pragma GCC diagnostic pop

  template<typename _Key, typename _Value, typename _Alloc,
@@ -2977,6 +3012,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
      = __enable_if_t<!__or_<is_integral<_Hash>, __is_allocator<_Hash>>::value>;
#endif

+#ifdef __cpp_concepts
+template <typename _Kt, typename _Container>
+  concept __heterogeneous_hash_key =
+    __transparent_comparator<typename _Container::hasher> &&
+    __transparent_comparator<typename _Container::key_equal> &&
+    __heterogeneous_key<_Kt, _Container>;
+#endif
+
/// @endcond
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
diff --git a/libstdc++-v3/include/bits/stl_function.h 
b/libstdc++-v3/include/bits/stl_function.h
index 659025c4bf9..7ebf1fb5799 100644
--- a/libstdc++-v3/include/bits/stl_function.h
+++ b/libstdc++-v3/include/bits/stl_function.h
@@ -1486,6 +1486,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
#endif
#endif

+#ifdef __cpp_concepts
+template <typename _Kt, typename _Container>
+  concept __not_container_iterator =
+    (!is_convertible_v<_Kt&&, typename _Container::iterator> &&
+     !is_convertible_v<_Kt&&, typename _Container::const_iterator>);
+
+template <typename _Kt, typename _Container>
+  concept __heterogeneous_key =
+    (!is_same_v<typename _Container::key_type, remove_cvref_t<_Kt>>) &&
+    __not_container_iterator<_Kt, _Container>;
+#endif
+
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

diff --git a/libstdc++-v3/include/bits/stl_map.h 
b/libstdc++-v3/include/bits/stl_map.h
index 6620bfee931..4cb0c982c3d 100644
--- a/libstdc++-v3/include/bits/stl_map.h
+++ b/libstdc++-v3/include/bits/stl_map.h
@@ -65,6 +65,7 @@
#if __glibcxx_containers_ranges // C++ >= 23
# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
#endif
+// #include <stl_tree.h>  // done in std/map

namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -679,6 +680,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      extract(const key_type& __x)
      { return _M_t.extract(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_tree_key<map> _Kt>
+       node_type
+       extract(_Kt&& __key)
+       { return _M_t._M_extract_tr(__key); }
+#endif
+
      /// Re-insert an extracted node.
      insert_return_type
      insert(node_type&& __nh)
@@ -1143,6 +1151,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      { _M_t.erase(__position); }
#endif

+      ///@{
      /**
       *  @brief Erases elements according to the provided key.
       *  @param  __x  Key of element to be erased.
@@ -1158,6 +1167,17 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      erase(const key_type& __x)
      { return _M_t._M_erase_unique(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      // Note that for some types _Kt this may erase more than
+      // one element, such as if _Kt::operator< checks only part
+      // of the key.
+      template <__heterogeneous_tree_key<map> _Kt>
+       size_type
+       erase(_Kt&& __x)
+       { return _M_t._M_erase_tr(__x); }
+#endif
+      ///@}
+
#if __cplusplus >= 201103L
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 130. Associative erase should return an iterator.
diff --git a/libstdc++-v3/include/bits/stl_multimap.h 
b/libstdc++-v3/include/bits/stl_multimap.h
index ea68401a88c..6f4e425d771 100644
--- a/libstdc++-v3/include/bits/stl_multimap.h
+++ b/libstdc++-v3/include/bits/stl_multimap.h
@@ -63,6 +63,7 @@
#if __glibcxx_containers_ranges // C++ >= 23
# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
#endif
+// #include <bits/stl_tree.h>  // done in std/map

namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -688,6 +689,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      extract(const key_type& __x)
      { return _M_t.extract(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_tree_key<multimap> _Kt>
+       node_type
+       extract(_Kt&& __key)
+       { return _M_t._M_extract_tr(__key); }
+#endif
+
      /// Re-insert an extracted node.
      iterator
      insert(node_type&& __nh)
@@ -787,6 +795,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      erase(const key_type& __x)
      { return _M_t.erase(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_tree_key<multimap> _Kt>
+       size_type
+       erase(_Kt&& __key)
+       { return _M_t._M_erase_tr(__key); }
+#endif
+
#if __cplusplus >= 201103L
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 130. Associative erase should return an iterator.
diff --git a/libstdc++-v3/include/bits/stl_multiset.h 
b/libstdc++-v3/include/bits/stl_multiset.h
index db950da50ea..64047ad7631 100644
--- a/libstdc++-v3/include/bits/stl_multiset.h
+++ b/libstdc++-v3/include/bits/stl_multiset.h
@@ -63,6 +63,7 @@
#if __glibcxx_containers_ranges // C++ >= 23
# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
#endif
+// #include <bits/stl_tree.h>  // done in std/set

namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -621,6 +622,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      extract(const key_type& __x)
      { return _M_t.extract(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_tree_key<multiset> _Kt>
+       node_type
+       extract(_Kt&& __key)
+       { return _M_t._M_extract_tr(__key); }
+#endif
+
      /// Re-insert an extracted node.
      iterator
      insert(node_type&& __nh)
@@ -712,6 +720,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      erase(const key_type& __x)
      { return _M_t.erase(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_tree_key<multiset> _Kt>
+       size_type
+       erase(_Kt&& __key)
+       { return _M_t._M_erase_tr(__key); }
+#endif
+
#if __cplusplus >= 201103L
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 130. Associative erase should return an iterator.
diff --git a/libstdc++-v3/include/bits/stl_set.h 
b/libstdc++-v3/include/bits/stl_set.h
index 96089fb172d..d3a4c089110 100644
--- a/libstdc++-v3/include/bits/stl_set.h
+++ b/libstdc++-v3/include/bits/stl_set.h
@@ -63,6 +63,7 @@
#if __glibcxx_containers_ranges // C++ >= 23
# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
#endif
+// #include <bits/stl_tree.h>  // done in std/set

namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -639,6 +640,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      extract(const key_type& __x)
      { return _M_t.extract(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_tree_key<set> _Kt>
+       node_type
+       extract(_Kt&& __key)
+       { return _M_t._M_extract_tr(__key); }
+#endif
+
      /// Re-insert an extracted node.
      insert_return_type
      insert(node_type&& __nh)
@@ -730,6 +738,16 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      erase(const key_type& __x)
      { return _M_t._M_erase_unique(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      // Note that for some types _Kt this may erase more than
+      // one element, such as if _Kt::operator< checks only part
+      // of the key.
+      template <__heterogeneous_tree_key<set> _Kt>
+       size_type
+       erase(_Kt&& __key)
+       { return _M_t._M_erase_tr(__key); }
+#endif
+
#if __cplusplus >= 201103L
      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 130. Associative erase should return an iterator.
diff --git a/libstdc++-v3/include/bits/stl_tree.h 
b/libstdc++-v3/include/bits/stl_tree.h
index 8f0aa731c93..7a733241ec5 100644
--- a/libstdc++-v3/include/bits/stl_tree.h
+++ b/libstdc++-v3/include/bits/stl_tree.h
@@ -1539,10 +1539,18 @@ namespace __rb_tree
      _M_lower_bound(_Base_ptr __x, _Base_ptr __y,
                     const _Key& __k) const;

+      template <typename _Kt>
+       _Base_ptr
+       _M_lower_bound_tr(_Base_ptr __x, _Base_ptr __y, const _Kt& __k) const;
+
      _Base_ptr
      _M_upper_bound(_Base_ptr __x, _Base_ptr __y,
                     const _Key& __k) const;

+      template <typename _Kt>
+       _Base_ptr
+       _M_upper_bound_tr(_Base_ptr __x, _Base_ptr __y, const _Kt& __k) const;
+
    public:
      // allocation/deallocation
#if __cplusplus < 201103L
@@ -1850,6 +1858,10 @@ namespace __rb_tree
      size_type
      erase(const key_type& __x);

+      template <typename _Kt>
+       size_type
+       _M_erase_tr(const _Kt& __x);
+
      size_type
      _M_erase_unique(const key_type& __x);

@@ -2198,6 +2210,17 @@ namespace __rb_tree
        return __nh;
      }

+      template <typename _Kt>
+       node_type
+       _M_extract_tr(const _Kt& __k)
+       {
+         node_type __nh;
+         auto __pos = _M_find_tr(__k);
+         if (__pos != end())
+           __nh = extract(const_iterator(__pos));
+         return __nh;
+       }
+
      template<typename _Compare2>
        using _Compatible_tree
          = _Rb_tree<_Key, _Val, _KeyOfValue, _Compare2, _Alloc>;
@@ -2612,6 +2635,21 @@ namespace __rb_tree
      return __y;
    }

+  template<typename _Key, typename _Val, typename _KeyOfValue,
+       typename _Compare, typename _Alloc>
+    template <typename _Kt>
+      typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr
+      _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
+      _M_lower_bound_tr(_Base_ptr __x, _Base_ptr __y, const _Kt& __k) const
+      {
+       while (__x)
+         if (!_M_key_compare(_S_key(__x), __k))
+           __y = __x, __x = _S_left(__x);
+         else
+           __x = _S_right(__x);
+       return __y;
+      }
+
  template<typename _Key, typename _Val, typename _KeyOfValue,
           typename _Compare, typename _Alloc>
    typename _Rb_tree<_Key, _Val, _KeyOfValue,
@@ -2628,6 +2666,21 @@ namespace __rb_tree
      return __y;
    }

+  template<typename _Key, typename _Val, typename _KeyOfValue,
+          typename _Compare, typename _Alloc>
+    template <typename _Kt>
+      typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr
+      _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
+      _M_upper_bound_tr(_Base_ptr __x, _Base_ptr __y, const _Kt& __k) const
+      {
+       while (__x)
+         if (_M_key_compare(__k, _S_key(__x)))
+           __y = __x, __x = _S_left(__x);
+         else
+           __x = _S_right(__x);
+       return __y;
+      }
+
  template<typename _Key, typename _Val, typename _KeyOfValue,
           typename _Compare, typename _Alloc>
    pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
@@ -3142,6 +3195,19 @@ namespace __rb_tree
      return __old_size - size();
    }

+  template<typename _Key, typename _Val, typename _KeyOfValue,
+          typename _Compare, typename _Alloc>
+    template <typename _Kt>
+      typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::size_type
+      _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
+      _M_erase_tr(const _Kt& __x)
+      {
+       pair<iterator, iterator> __p = _M_equal_range_tr(__x);
+       const size_type __old_size = size();
+       _M_erase_aux(__p.first, __p.second);
+       return __old_size - size();
+      }
+
  template<typename _Key, typename _Val, typename _KeyOfValue,
           typename _Compare, typename _Alloc>
    typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::size_type
@@ -3249,6 +3315,13 @@ namespace __rb_tree
    };
#endif // C++17

+#ifdef __cpp_concepts
+template <typename _Kt, typename _Container>
+  concept __heterogeneous_tree_key =
+    __transparent_comparator<typename _Container::key_compare> &&
+    __heterogeneous_key<_Kt, _Container>;
+#endif
+
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

diff --git a/libstdc++-v3/include/bits/unordered_map.h 
b/libstdc++-v3/include/bits/unordered_map.h
index 36f5a8d91c3..9b74cba8675 100644
--- a/libstdc++-v3/include/bits/unordered_map.h
+++ b/libstdc++-v3/include/bits/unordered_map.h
@@ -501,6 +501,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      extract(const key_type& __key)
      { return _M_h.extract(__key); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_hash_key<unordered_map> _Kt>
+       node_type
+       extract(_Kt&& __key)
+       { return _M_h._M_extract_tr(__key); }
+#endif
+
      /// Re-insert an extracted node.
      insert_return_type
      insert(node_type&& __nh)
@@ -848,6 +855,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      erase(const key_type& __x)
      { return _M_h.erase(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_hash_key<unordered_map> _Kt>
+       size_type
+       erase(_Kt&& __key)
+       { return _M_h._M_erase_tr(__key); }
+#endif
+
      /**
       *  @brief Erases a [__first,__last) range of elements from an
       *  %unordered_map.
@@ -1872,6 +1886,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      extract(const key_type& __key)
      { return _M_h.extract(__key); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_hash_key<unordered_multimap> _Kt>
+       node_type
+       extract(_Kt&& __key)
+       { return _M_h._M_extract_tr(__key); }
+#endif
+
      /// Re-insert an extracted node.
      iterator
      insert(node_type&& __nh)
@@ -1922,6 +1943,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      erase(const key_type& __x)
      { return _M_h.erase(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_hash_key<unordered_multimap> _Kt>
+       size_type
+       erase(_Kt&& __key)
+       { return _M_h._M_erase_tr(__key); }
+#endif
+
      /**
       *  @brief Erases a [__first,__last) range of elements from an
       *  %unordered_multimap.
diff --git a/libstdc++-v3/include/bits/unordered_set.h 
b/libstdc++-v3/include/bits/unordered_set.h
index 3a96229e1f5..22b2ad9caf4 100644
--- a/libstdc++-v3/include/bits/unordered_set.h
+++ b/libstdc++-v3/include/bits/unordered_set.h
@@ -581,6 +581,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      extract(const key_type& __key)
      { return _M_h.extract(__key); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_hash_key<unordered_set> _Kt>
+       node_type
+       extract(_Kt&& __key)
+       { return _M_h._M_extract_tr(__key); }
+#endif
+
      /// Re-insert an extracted node.
      insert_return_type
      insert(node_type&& __nh)
@@ -632,6 +639,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      erase(const key_type& __x)
      { return _M_h.erase(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_hash_key<unordered_set> _Kt>
+       size_type
+       erase(_Kt&& __key)
+       { return _M_h._M_erase_tr(__key); }
+#endif
+
      /**
       *  @brief Erases a [__first,__last) range of elements from an
       *  %unordered_set.
@@ -1574,6 +1588,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      extract(const key_type& __key)
      { return _M_h.extract(__key); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_hash_key<unordered_multiset> _Kt>
+       node_type
+       extract(_Kt&& __key)
+       { return _M_h._M_extract_tr(__key); }
+#endif
+
      /// Re-insert an extracted node.
      iterator
      insert(node_type&& __nh)
@@ -1627,6 +1648,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
      erase(const key_type& __x)
      { return _M_h.erase(__x); }

+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+      template <__heterogeneous_hash_key<unordered_multiset> _Kt>
+       size_type
+       erase(_Kt&& __key)
+       { return _M_h._M_erase_tr(__key); }
+#endif
+
      /**
       *  @brief Erases a [__first,__last) range of elements from an
       *  %unordered_multiset.
diff --git a/libstdc++-v3/include/bits/version.def 
b/libstdc++-v3/include/bits/version.def
index 167e5112995..937ea09ad78 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1617,6 +1617,14 @@ ftms = {
  };
};

+ftms = {
+  name = associative_heterogeneous_erasure;
+  values = {
+    v = 202110;
+    cxxmin = 23;
+  };
+};
+
ftms = {
  name = is_scoped_enum;
  values = {
diff --git a/libstdc++-v3/include/bits/version.h 
b/libstdc++-v3/include/bits/version.h
index 77829c6606d..ed1794f4c0d 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -1781,6 +1781,16 @@
#endif /* !defined(__cpp_lib_invoke_r) */
#undef __glibcxx_want_invoke_r

+#if !defined(__cpp_lib_associative_heterogeneous_erasure)
+# if (__cplusplus >= 202100L)
+#  define __glibcxx_associative_heterogeneous_erasure 202110L
+#  if defined(__glibcxx_want_all) || 
defined(__glibcxx_want_associative_heterogeneous_erasure)
+#   define __cpp_lib_associative_heterogeneous_erasure 202110L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_associative_heterogeneous_erasure) */
+#undef __glibcxx_want_associative_heterogeneous_erasure
+
#if !defined(__cpp_lib_is_scoped_enum)
# if (__cplusplus >= 202100L)
#  define __glibcxx_is_scoped_enum 202011L
diff --git a/libstdc++-v3/include/std/map b/libstdc++-v3/include/std/map
index b4d39959f0d..f3a1a9ea996 100644
--- a/libstdc++-v3/include/std/map
+++ b/libstdc++-v3/include/std/map
@@ -79,6 +79,7 @@
#define __glibcxx_want_node_extract
#define __glibcxx_want_nonmember_container_access
#define __glibcxx_want_tuple_like
+#define __glibcxx_want_associative_heterogeneous_erasure
#include <bits/version.h>

#if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/set b/libstdc++-v3/include/std/set
index 95b9e26a99f..0866ad4dbb2 100644
--- a/libstdc++-v3/include/std/set
+++ b/libstdc++-v3/include/std/set
@@ -77,6 +77,7 @@
#define __glibcxx_want_generic_associative_lookup
#define __glibcxx_want_node_extract
#define __glibcxx_want_nonmember_container_access
+#define __glibcxx_want_associative_heterogeneous_erasure
#include <bits/version.h>

#if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/unordered_map 
b/libstdc++-v3/include/std/unordered_map
index c49c5c6835f..b5aab614309 100644
--- a/libstdc++-v3/include/std/unordered_map
+++ b/libstdc++-v3/include/std/unordered_map
@@ -56,6 +56,7 @@
#define __glibcxx_want_nonmember_container_access
#define __glibcxx_want_unordered_map_try_emplace
#define __glibcxx_want_tuple_like
+#define __glibcxx_want_associative_heterogeneous_erasure
#include <bits/version.h>

#if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/unordered_set 
b/libstdc++-v3/include/std/unordered_set
index 512d542a0b5..57c2aa5e205 100644
--- a/libstdc++-v3/include/std/unordered_set
+++ b/libstdc++-v3/include/std/unordered_set
@@ -54,6 +54,7 @@
#define __glibcxx_want_generic_unordered_lookup
#define __glibcxx_want_node_extract
#define __glibcxx_want_nonmember_container_access
+#define __glibcxx_want_associative_heterogeneous_erasure
#include <bits/version.h>

#if __cplusplus >= 201703L
diff --git a/libstdc++-v3/testsuite/23_containers/map/modifiers/hetero/erase.cc 
b/libstdc++-v3/testsuite/23_containers/map/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..20e0cbad261
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/map/modifiers/hetero/erase.cc
@@ -0,0 +1,95 @@
+// { dg-do run { target c++23 } }
+
+#include <map>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <compare>
+#include <cstring>
+#include <testsuite_hooks.h>
+
+struct X {
+  std::string s;
+  friend auto operator<=>(X const& a, X const& b) = default;
+};
+
+struct Y {
+  std::string_view s;
+  Y(std::string_view sv) : s(sv) {}
+  Y(std::string const& sv) : s(sv) {}
+  friend auto operator<=>(Y const& a, Y const& b) = default;
+  friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
+};
+
+using cmp = std::less<void>;
+
+// uniform erase
+void test1()
+{
+  std::map<X, int, cmp> amap{cmp{}};
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto n = amap.erase(X{std::string{"def"}});
+  VERIFY(n == 1);
+  VERIFY(amap.size() == 2);
+}
+
+// heterogeneous erase
+void test2()
+{
+  std::map<X, int, cmp> amap{cmp{}};
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto n = amap.erase(Y{std::string_view{"def"}});
+  VERIFY(n == 1);
+  VERIFY(amap.size() == 2);
+}
+
+// uniform extract
+void test3()
+{
+  std::map<X, int, cmp> amap{cmp{}};
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto node = amap.extract(X{std::string{"def"}});
+  VERIFY(node.key().s == X{"def"}.s);
+  VERIFY(node.mapped() == 2);
+  VERIFY(amap.size() == 2);
+}
+
+// heterogeneous extract
+void test4()
+{
+  std::map<X, int, cmp> amap{cmp{}};
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto node = amap.extract(Y{std::string_view{"def"}});
+  VERIFY(node.key().s == X{"def"}.s);
+  VERIFY(node.mapped() == 2);
+  VERIFY(amap.size() == 2);
+}
+
+struct Z {
+  std::string_view s;
+  friend auto operator<=>(X const& a, Z const& b) {
+    int cmp = std::memcmp(a.s.data(), b.s.data(), 3);
+    return cmp < 0 ? std::strong_ordering::less :
+          cmp == 0 ? std::strong_ordering::equal :
+          std::strong_ordering::greater;
+  }
+};
+
+void test5()
+{
+  std::map<X, int, cmp> amap{cmp{}};
+  amap.insert(
+    {{X{"abcdef"}, 1}, {X{"defghi"}, 2}, {X{"defjkl"}, 3}, {X{"jklmno"}, 4}});
+  auto n = amap.erase(Z{std::string_view{"def"}});
+  VERIFY(n == 2);
+  VERIFY(amap.size() == 2);
+}
+
+int main()
+{
+  test1();
+  test2();
+  test3();
+  test4();
+  test5();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/multimap/modifiers/hetero/erase.cc 
b/libstdc++-v3/testsuite/23_containers/multimap/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..bd4a1520c2b
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/multimap/modifiers/hetero/erase.cc
@@ -0,0 +1,95 @@
+// { dg-do run { target c++23 } }
+
+#include <map>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <compare>
+#include <cstring>
+#include <testsuite_hooks.h>
+
+struct X {
+  std::string s;
+  friend auto operator<=>(X const& a, X const& b) = default;
+};
+
+struct Y {
+  std::string_view s;
+  Y(std::string_view sv) : s(sv) {}
+  Y(std::string const& sv) : s(sv) {}
+  friend auto operator<=>(Y const& a, Y const& b) = default;
+  friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
+};
+
+using cmp = std::less<void>;
+
+// uniform erase
+void test1()
+{
+  std::multimap<X, int, cmp> amap{cmp{}};
+  amap.insert({{X{"abc"}, 0}, {X{"def"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto n = amap.erase(X{std::string{"def"}});
+  VERIFY(n == 2);
+  VERIFY(amap.size() == 2);
+}
+
+// heterogeneous erase
+void test2()
+{
+  std::multimap<X, int, cmp> amap{cmp{}};
+  amap.insert({{X{"abc"}, 0}, {X{"def"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto n = amap.erase(Y{std::string_view{"def"}});
+  VERIFY(n == 2);
+  VERIFY(amap.size() == 2);
+}
+
+// uniform extract
+void test3()
+{
+  std::multimap<X, int, cmp> amap{cmp{}};
+  amap.insert({{X{"abc"}, 0}, {X{"def"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  const auto node = amap.extract(X{std::string{"def"}});
+  VERIFY(node.key().s == "def");
+  VERIFY(node.mapped() == 1);
+  VERIFY(amap.size() == 3);
+}
+
+// heterogeneous extract
+void test4()
+{
+  std::multimap<X, int, cmp> amap{cmp{}};
+  amap.insert({{X{"abc"}, 0}, {X{"def"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  const auto node = amap.extract(Y{std::string_view{"def"}});
+  VERIFY(node.key().s == "def");
+  VERIFY(node.mapped() == 1);
+  VERIFY(amap.size() == 3);
+}
+
+struct Z {
+  std::string_view s;
+  friend auto operator<=>(X const& a, Z const& b) {
+    int cmp = std::memcmp(a.s.data(), b.s.data(), 3);
+    return cmp < 0 ? std::strong_ordering::less :
+          cmp == 0 ? std::strong_ordering::equal :
+          std::strong_ordering::greater;
+  }
+};
+
+void test5()
+{
+  std::map<X, int, cmp> amap{cmp{}};
+  amap.insert(
+    {{X{"abcdef"}, 1}, {X{"defghi"}, 2}, {X{"defjkl"}, 3}, {X{"jklmno"}, 4}});
+  auto n = amap.erase(Z{std::string_view{"def"}});
+  VERIFY(n == 2);
+  VERIFY(amap.size() == 2);
+}
+
+int main()
+{
+  test1();
+  test2();
+  test3();
+  test4();
+  test5();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/multiset/modifiers/hetero/erase.cc 
b/libstdc++-v3/testsuite/23_containers/multiset/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..8ebbc2604ba
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/multiset/modifiers/hetero/erase.cc
@@ -0,0 +1,88 @@
+// { dg-do run { target c++23 } }
+
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <compare>
+#include <cstring>
+#include <testsuite_hooks.h>
+
+struct X {
+  std::string s;
+  friend auto operator<=>(X const& a, X const& b) = default;
+};
+
+struct Y {
+  std::string_view s;
+  Y(std::string_view sv) : s(sv) {}
+  Y(std::string const& sv) : s(sv) {}
+  friend auto operator<=>(Y const& a, Y const& b) = default;
+  friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
+};
+
+using cmp = std::less<void>;
+
+void test1() // uniform erase
+{
+  std::multiset<X, cmp> aset{cmp{}};
+  aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+  auto n = aset.erase(X{std::string{"def"}});
+  VERIFY(n == 2);
+  VERIFY(aset.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+  std::multiset<X, cmp> aset{cmp{}};
+  aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+  auto n = aset.erase(Y{std::string_view{"def"}});
+  VERIFY(n == 2);
+  VERIFY(aset.size() == 2);
+}
+
+void test3() // uniform extract
+{
+  std::multiset<X, cmp> aset{cmp{}};
+  aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+  const auto node = aset.extract(X{std::string{"def"}});
+  VERIFY(node.value().s == "def");
+  VERIFY(aset.size() == 3);
+}
+
+void test4() // heterogeneous extract
+{
+  std::multiset<X, cmp> aset{cmp{}};
+  aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+  const auto node = aset.extract(Y{std::string_view{"def"}});
+  VERIFY(node.value().s == "def");
+  VERIFY(aset.size() == 3);
+}
+
+struct Z {
+  std::string_view s;
+  friend auto operator<=>(X const& a, Z const& b) {
+    int cmp = std::memcmp(a.s.data(), b.s.data(), 3);
+    return cmp < 0 ? std::strong_ordering::less :
+          cmp == 0 ? std::strong_ordering::equal :
+          std::strong_ordering::greater;
+  }
+};
+
+void test5()
+{
+  std::multiset<X, cmp> aset{cmp{}};
+  aset.insert({X{"abcdef"}, X{"defghi"}, X{"defjkl"}, X{"jklmno"}});
+  auto n = aset.erase(Z{std::string_view{"def"}});
+  VERIFY(n == 2);
+  VERIFY(aset.size() == 2);
+}
+
+int main()
+{
+  test1();
+  test2();
+  test3();
+  test4();
+  test5();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/set/modifiers/hetero/erase.cc 
b/libstdc++-v3/testsuite/23_containers/set/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..8377a3f5728
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/set/modifiers/hetero/erase.cc
@@ -0,0 +1,88 @@
+// { dg-do run { target c++23 } }
+
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <compare>
+#include <cstring>
+#include <testsuite_hooks.h>
+
+struct X {
+  std::string s;
+  friend auto operator<=>(X const& a, X const& b) = default;
+};
+
+struct Y {
+  std::string_view s;
+  Y(std::string_view sv) : s(sv) {}
+  Y(std::string const& sv) : s(sv) {}
+  friend auto operator<=>(Y const& a, Y const& b) = default;
+  friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
+};
+
+using cmp = std::less<void>;
+
+void test1()  // uniform erase
+{
+  std::set<X, cmp> aset{cmp{}};
+  aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+  auto n = aset.erase(X{std::string{"def"}});
+  VERIFY(n == 1);
+  VERIFY(aset.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+  std::set<X, cmp> aset{cmp{}};
+  aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+  auto n = aset.erase(Y{std::string_view{"def"}});
+  VERIFY(n == 1);
+  VERIFY(aset.size() == 2);
+}
+
+void test3() // uniform extract
+{
+  std::set<X, cmp> aset{cmp{}};
+  aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+  auto node = aset.extract(X{std::string{"def"}});
+  VERIFY(node.value().s == X{"def"}.s);
+  VERIFY(aset.size() == 2);
+}
+
+void test4() // heterogeneous extract
+{
+  std::set<X, cmp> aset{cmp{}};
+  aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+  auto node = aset.extract(Y{std::string_view{"def"}});
+  VERIFY(node.value().s == X{"def"}.s);
+  VERIFY(aset.size() == 2);
+}
+
+struct Z {
+  std::string_view s;
+  friend auto operator<=>(X const& a, Z const& b) {
+    int cmp = std::memcmp(a.s.data(), b.s.data(), 3);
+    return cmp < 0 ? std::strong_ordering::less :
+          cmp == 0 ? std::strong_ordering::equal :
+          std::strong_ordering::greater;
+  }
+};
+
+void test5()
+{
+  std::set<X, cmp> aset{cmp{}};
+  aset.insert({X{"abcdef"}, X{"defghi"}, X{"defjkl"}, X{"jklmno"}});
+  auto n = aset.erase(Z{std::string_view{"def"}});
+  VERIFY(n == 2);
+  VERIFY(aset.size() == 2);
+}
+
+int main()
+{
+  test1();
+  test2();
+  test3();
+  test4();
+  test5();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc 
b/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..976b574fde2
--- /dev/null
+++ 
b/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc
@@ -0,0 +1,76 @@
+// { dg-do run { target c++23 } }
+
+#include <unordered_map>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <functional>
+#include <compare>
+#include <testsuite_hooks.h>
+
+struct X {
+  std::string s;
+  friend bool operator==(X const& a, X const& b) = default;
+};
+
+struct Y {
+  std::string_view s;
+  Y(std::string_view sv) : s(sv) {}
+  Y(std::string const& sv) : s(sv) {}
+  friend bool operator==(Y const& a, Y const& b) = default;
+  friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
+};
+
+struct Hash {
+  using is_transparent = void;
+  template <typename T>
+    auto operator()(T const& t) const { return 
std::hash<decltype(T::s)>{}(t.s); }
+};
+
+using Equal = std::equal_to<void>;
+
+void test1() // uniform erase
+{
+  std::unordered_map<X, int, Hash, Equal> amap;
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto n = amap. erase(X{ std::string{"def"}});
+  VERIFY(n == 1);
+  VERIFY(amap.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+  std::unordered_map<X, int, Hash, Equal> amap;
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto n = amap. erase(Y{ std::string_view{"def"}});
+  VERIFY(n == 1);
+  VERIFY(amap.size() == 2);
+}
+
+void test3() // uniform extract
+{
+  std::unordered_map<X, int, Hash, Equal> amap;
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto node = amap. extract(X{ std::string{"def"}});
+  VERIFY(node.key().s == X{"def"}.s);
+  VERIFY(node.mapped() == 2);
+  VERIFY(amap.size() == 2);
+}
+
+void test4() // heterogeneous extract
+{
+  std::unordered_map<X, int, Hash, Equal> amap;
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+  auto node = amap. extract(Y{ std::string_view{"def"}});
+  VERIFY(node.key().s == X{"def"}.s);
+  VERIFY(node.mapped() == 2);
+  VERIFY(amap.size() == 2);
+}
+
+int main()
+{
+  test1();
+  test2();
+  test3();
+  test4();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc
 
b/libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..df8f2161aa6
--- /dev/null
+++ 
b/libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc
@@ -0,0 +1,75 @@
+// { dg-do run { target c++23 } }
+
+#include <unordered_map>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <functional>
+#include <testsuite_hooks.h>
+
+struct X {
+  std::string s;
+  friend bool operator==(X const& a, X const& b) = default;
+};
+
+struct Y {
+  std::string_view s;
+  Y(std::string_view sv) : s(sv) {}
+  Y(std::string const& sv) : s(sv) {}
+  friend bool operator==(Y const& a, Y const& b) = default;
+  friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
+};
+
+struct Hash {
+  using is_transparent = void;
+  template <typename T>
+    auto operator()(T const& t) const { return 
std::hash<decltype(T::s)>{}(t.s); }
+};
+
+using Equal = std::equal_to<void>;
+
+void test1()  // uniform erase
+{
+  std::unordered_multimap<X, int, Hash, Equal> amap;
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"def"}, 3}, {X{"ghi"}, 4}});
+  auto n = amap. erase(X{ std::string{"def"}});
+  VERIFY(n == 2);
+  VERIFY(amap.size() == 2);
+}
+
+void test2()  // heterogeneous erase
+{
+  std::unordered_multimap<X, int, Hash, Equal> amap;
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"def"}, 3}, {X{"ghi"}, 4}});
+  auto n = amap. erase(Y{ std::string_view{"def"}});
+  VERIFY(n == 2);
+  VERIFY(amap.size() == 2);
+}
+
+void test3()  // uniform extract
+{
+  std::unordered_multimap<X, int, Hash, Equal> amap;
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"def"}, 3}, {X{"ghi"}, 4}});
+  auto node = amap. extract(X{ std::string{"def"}});
+  VERIFY(node.key().s == X{"def"}.s);
+  VERIFY(node.mapped() == 2 || node.mapped() == 3);
+  VERIFY(amap.size() == 3);
+}
+
+void test4()  // heterogeneous extract
+{
+  std::unordered_multimap<X, int, Hash, Equal> amap;
+  amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"def"}, 3}, {X{"ghi"}, 4}});
+  auto node = amap. extract(Y{ std::string_view{"def"}});
+  VERIFY(node.key().s == X{"def"}.s);
+  VERIFY(node.mapped() == 2 || node.mapped() == 3);
+  VERIFY(amap.size() == 3);
+}
+
+int main()
+{
+  test1();
+  test2();
+  test3();
+  test4();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc
 
b/libstdc++-v3/testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..e886abe2b54
--- /dev/null
+++ 
b/libstdc++-v3/testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc
@@ -0,0 +1,73 @@
+// { dg-do run { target c++23 } }
+
+#include <unordered_set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <functional>
+#include <testsuite_hooks.h>
+
+struct X {
+  std::string s;
+  friend bool operator==(X const& a, X const& b) = default;
+};
+
+struct Y {
+  std::string_view s;
+  Y(std::string_view sv) : s(sv) {}
+  Y(std::string const& sv) : s(sv) {}
+  friend bool operator==(Y const& a, Y const& b) = default;
+  friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
+};
+
+struct Hash {
+  using is_transparent = void;
+  template <typename T>
+    auto operator()(T const& t) const { return 
std::hash<decltype(T::s)>{}(t.s); }
+};
+
+using Equal = std::equal_to<void>;
+
+void test1() // uniform erase
+{
+  std::unordered_multiset<X, Hash, Equal> aset;
+  aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+  auto n = aset. erase(X{ std::string{"def"}});
+  VERIFY(n == 2);
+  VERIFY(aset.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+  std::unordered_multiset<X, Hash, Equal> aset;
+  aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+  auto n = aset. erase(Y{ std::string_view{"def"}});
+  VERIFY(n == 2);
+  VERIFY(aset.size() == 2);
+}
+
+void test3() // uniform extract
+{
+  std::unordered_multiset<X, Hash, Equal> aset;
+  aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+  auto node = aset. extract(X{ std::string{"def"}});
+  VERIFY(node.value().s == X{"def"}.s);
+  VERIFY(aset.size() == 3);
+}
+
+void test4() // heterogeneous extract
+{
+  std::unordered_multiset<X, Hash, Equal> aset;
+  aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+  auto node = aset. extract(Y{ std::string_view{"def"}});
+  VERIFY(node.value().s == X{"def"}.s);
+  VERIFY(aset.size() == 3);
+}
+
+int main()
+{
+  test1();
+  test2();
+  test3();
+  test4();
+}
diff --git 
a/libstdc++-v3/testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc 
b/libstdc++-v3/testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..6b60fa40198
--- /dev/null
+++ 
b/libstdc++-v3/testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc
@@ -0,0 +1,73 @@
+// { dg-do run { target c++23 } }
+
+#include <unordered_set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <functional>
+#include <testsuite_hooks.h>
+
+struct X {
+  std::string s;
+  friend bool operator==(X const& a, X const& b) = default;
+};
+
+struct Y {
+  std::string_view s;
+  Y(std::string_view sv) : s(sv) {}
+  Y(std::string const& sv) : s(sv) {}
+  friend bool operator==(Y const& a, Y const& b) = default;
+  friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
+};
+
+struct Hash {
+  using is_transparent = void;
+  template <typename T>
+    auto operator()(T const& t) const { return 
std::hash<decltype(T::s)>{}(t.s); }
+};
+
+using Equal = std::equal_to<void>;
+
+void test1()  // uniform erase
+{
+  std::unordered_set<X, Hash, Equal> aset;
+  aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+  auto n = aset.erase(X{std::string{"def"}});
+  VERIFY(n == 1);
+  VERIFY(aset.size() == 2);
+}
+
+void test2()  // heterogeneous erase
+{
+  std::unordered_set<X, Hash, Equal> aset;
+  aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+  auto n = aset.erase(Y{std::string_view{"def"}});
+  VERIFY(n == 1);
+  VERIFY(aset.size() == 2);
+}
+
+void test3() // uniform extract
+{
+  std::unordered_set<X, Hash, Equal> aset;
+  aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+  auto node = aset.extract(X{std::string{"def"}});
+  VERIFY(node.value().s == X{"def"}.s);
+  VERIFY(aset.size() == 2);
+}
+
+void test4()  // heterogeneous extract
+{
+  std::unordered_set<X, Hash, Equal> aset;
+  aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+  auto node = aset.extract(Y{std::string_view{"def"}});
+  VERIFY(node.value().s == X{"def"}.s);
+  VERIFY(aset.size() == 2);
+}
+
+int main()
+{
+  test1();
+  test2();
+  test3();
+  test4();
+}
--
2.52.0



Reply via email to