[PATCH] libstdc++: container erasure overloads (P2077) [PR117404]

Nathan Myers ncm@cantrip.org
Thu Nov 13 20:00:58 GMT 2025


Thank you.

On 11/12/25 4:12 AM, Tomasz Kaminski wrote:
> 
> 
> On Sun, Nov 9, 2025 at 8:58 AM Nathan Myers <ncm@cantrip.org 
> <mailto:ncm@cantrip.org>> wrote:
> 
>     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.
> 
>     Header inclusion order is adjusted to make version.h symbols
>     available in more places.
> 
>     libstdc++-v3/ChangeLog:
>              PR libstdc++/117404
>              * include/bits/version.def: Add feature macro
>              __cpplib_heterogeneous_erasure.
>              * 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.
> 
> The std/debug/files needs to be also updated. But you may do this as 
> separate commit.

Got it.

>              * include/bits/stl_map.h: Add specified new 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.
>              * include/bits/unordered_set.h: Same.
>              * include/bits/hashtable.h: Add supporting overloads, new
>     concept
>              __heterogeneous_hash_key.
>              * include/bits/stl_tree.h: Add supporting overloads, new
>     concept
>              __heterogeneous_tree_key.
>              * include/bits/stl_function.h: Add concept __heterogeneous_key.
>              * testsuite/23_containers/map/modifiers/erase/p2077.cc: New
>     test.
>              * testsuite/23_containers/multimap/modifiers/erase/
>     p2077.cc: Same.
>              * testsuite/23_containers/multiset/modifiers/erase/
>     p2077.cc: Same.
>              * testsuite/23_containers/set/modifiers/erase/p2077.cc: Same.
>              * testsuite/23_containers/unordered_map/modifiers/p2077.cc:
>     Same.
>              * testsuite/23_containers/unordered_multimap/modifiers/
>     p2077.cc: Same.
>              * testsuite/23_containers/unordered_multiset/modifiers/
>     p2077.cc: Same.
>              * testsuite/23_containers/unordered_set/modifiers/p2077.cc:
>     Same.
>     ---
>       libstdc++-v3/include/bits/hashtable.h         | 139 ++++++++++++++++++
>       libstdc++-v3/include/bits/stl_function.h      |   9 ++
>       libstdc++-v3/include/bits/stl_map.h           |  13 ++
>       libstdc++-v3/include/bits/stl_multimap.h      |  13 ++
>       libstdc++-v3/include/bits/stl_multiset.h      |  13 ++
>       libstdc++-v3/include/bits/stl_set.h           |  13 ++
>       libstdc++-v3/include/bits/stl_tree.h          | 120 ++++++++++++++-
>       libstdc++-v3/include/bits/unordered_map.h     |  24 +++
>       libstdc++-v3/include/bits/unordered_set.h     |  24 +++
>       libstdc++-v3/include/bits/version.def         |   8 +
>       libstdc++-v3/include/bits/version.h           |  12 +-
>       libstdc++-v3/include/std/map                  |  22 +--
>       libstdc++-v3/include/std/set                  |  19 +--
>       libstdc++-v3/include/std/unordered_map        |  19 +--
>       libstdc++-v3/include/std/unordered_set        |  17 ++-
>       .../map/modifiers/erase/p2077.cc              |  91 ++++++++++++
>       .../multimap/modifiers/erase/p2077.cc         |  91 ++++++++++++
>       .../multiset/modifiers/erase/p2077.cc         |  85 +++++++++++
>       .../set/modifiers/erase/p2077.cc              |  85 +++++++++++
>       .../unordered_map/modifiers/p2077.cc          |  94 ++++++++++++
>       .../unordered_multimap/modifiers/p2077.cc     |  94 ++++++++++++
>       .../unordered_multiset/modifiers/p2077.cc     |  92 ++++++++++++
>       .../unordered_set/modifiers/p2077.cc          |  92 ++++++++++++
>       23 files changed, 1149 insertions(+), 40 deletions(-)
>       create mode 100644 libstdc++-v3/testsuite/23_containers/map/
>     modifiers/erase/p2077.cc
>       create mode 100644 libstdc++-v3/testsuite/23_containers/multimap/
>     modifiers/erase/p2077.cc
>       create mode 100644 libstdc++-v3/testsuite/23_containers/multiset/
>     modifiers/erase/p2077.cc
>       create mode 100644 libstdc++-v3/testsuite/23_containers/set/
>     modifiers/erase/p2077.cc
>       create mode 100644 libstdc++-v3/testsuite/23_containers/
>     unordered_map/modifiers/p2077.cc
>       create mode 100644 libstdc++-v3/testsuite/23_containers/
>     unordered_multimap/modifiers/p2077.cc
>       create mode 100644 libstdc++-v3/testsuite/23_containers/
>     unordered_multiset/modifiers/p2077.cc
>       create mode 100644 libstdc++-v3/testsuite/23_containers/
>     unordered_set/modifiers/p2077.cc
> 
>     diff --git a/libstdc++-v3/include/bits/hashtable.h b/libstdc++-v3/
>     include/bits/hashtable.h
>     index 06cc51ac4a0..a8a844b40d8 100644
>     --- a/libstdc++-v3/include/bits/hashtable.h
>     +++ b/libstdc++-v3/include/bits/hashtable.h
>     @@ -905,6 +905,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>             __location_type
>             _M_locate(const key_type& __k) const;
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      template <typename _HetKey>
>     +       __location_type
>     +       _M_locate_tr(const _HetKey& __k) const;
>     +#endif
>     +
>             __node_ptr
>             _M_find_node(size_type __bkt, const key_type& __key,
>                         __hash_code __c) const
>     @@ -1163,6 +1169,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>             size_type
>             erase(const key_type& __k);
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      template <typename _HetKey>
>     +       size_type
>     +       _M_erase_tr(const _HetKey& __k);
>     +#endif
>     +
>             iterator
>             erase(const_iterator, const_iterator);
> 
>     @@ -1283,6 +1295,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>              return __nh;
>             }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      template <typename _HetKey>
>     +       node_type
>     +       _M_extract_tr(const _HetKey& __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;
>     +       }
>     +#endif
>     +
>             /// Merge from another container of the same type.
>             void
>             _M_merge_unique(_Hashtable& __src)
>     @@ -2289,6 +2316,45 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>             return __loc;
>           }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +  template<typename _Key, typename _Value, typename _Alloc,
>     +          typename _ExtractKey, typename _Equal,
>     +          typename _Hash, typename _RangeHash, typename _Unused,
>     +          typename _RehashPolicy, typename _Traits>
>     +    template <typename _HetKey>
>     +      inline auto
>     +      _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
>     +                _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
>     +      _M_locate_tr(const _HetKey& __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_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_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_tr(
>     +           __loc._M_bucket_index, __k, __loc._M_hash_code);
>     +
>     +       return __loc;
>     +      }
>     +#endif
>     +
>         template<typename _Key, typename _Value, typename _Alloc,
>                 typename _ExtractKey, typename _Equal,
>                 typename _Hash, typename _RangeHash, typename _Unused,
>     @@ -2598,6 +2664,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,
>     @@ -2658,6 +2725,70 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>                return __result;
>              }
>           }
>     +
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +  template<typename _Key, typename _Value, typename _Alloc,
>     +          typename _ExtractKey, typename _Equal,
>     +          typename _Hash, typename _RangeHash, typename _Unused,
>     +          typename _RehashPolicy, typename _Traits>
>     +    template <typename _HetKey>
>     +      auto
>     +      _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
>     +                _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
>     +      _M_erase_tr(const _HetKey& __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)
> 
> equal(__k, __element) may form an equivalence relation, and there may be 
> more
> than one element equal to __k even for non-multimap. Consider a 
> StringPrefix wrapper
> over string_view, such comparison against the prefix of the string_view. 
> From what I read this
> satisfies requirements on the key: https://eel.is/c++draft/ 
> containers#associative.reqmts.general-7.23 <https://eel.is/c++draft/ 
> containers#associative.reqmts.general-7.23>.
> An erase is specified to remove all these elements: https://eel.is/c+ 
> +draft/containers#associative.reqmts.general-123 <https://eel.is/c+ 
> +draft/containers#associative.reqmts.general-123>.

Good catch! I will need to add tests for that usage.

It seems more tricky even than this, because the set of matching
elements need not be a contiguous range. I think we need to move
the nodes to be erased to a local bucket as they are discovered,
before freeing them.

> In short, we need to do loop even for unique keys.
> I would do something like:
> 
> __node_ptr __n_last = __n->_M_next();
> while (__n_last && this->_M_node_equals(*__n, *__n_last))
>        __n_last = __n_last->_M_next();
> 
> if constexpr (__unique_keys::value)
>     if (_n->_M_next() == _n_last) [[likely]]
>     {
>          _M_erase(__bkt, __prev_n, __n);
>          return 1;
>     }
> Or even without constexpr.

Yes.

>     +         {
>     +           _M_erase(__bkt, __prev_n, __n);
>     +           return 1;
>     +         }
>     +       else
>     +         {
>     +           // _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;
>     +         }
>     +      }
>     +#endif // P2207
>       #pragma GCC diagnostic pop
> 
>         template<typename _Key, typename _Value, typename _Alloc,
>     @@ -2977,6 +3108,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>             = __enable_if_t<!__or_<is_integral<_Hash>,
>     __is_allocator<_Hash>>::value>;
>       #endif
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +template <typename _Kt, typename _Container>
>     +  concept __heterogeneous_hash_key =
>     +    __heterogeneous_key<_Kt, remove_cvref_t<_Container>> &&
>     +    __transparent_comparator<typename
>     remove_cvref_t<_Container>::hasher> &&
>     +    __transparent_comparator<typename
>     remove_cvref_t<_Container>::key_equal>;
> 
> Because the operands on the && in concept are short-circuited I would 
> check the
> __transparent_comparator<typename remove_cvref_t<_Container>::hasher>
> __transparent_comparator<typename remove_cvref_t<_Container>::key_equal>
> first, before __heterogeneous_hash_key. For given container 
> specialization they give the same
> result, and the compiler caches the values of concepts, so we will exit 
> early, before checking is_convertible.
> So concrete suggestion is:
> +  concept __heterogeneous_hash_key =
> +   __transparent_comparator<typename remove_cvref_t<_Container>::hasher> &&
> +    __transparent_comparator<typename 
> remove_cvref_t<_Container>::key_equal> &&
> +  __heterogeneous_key<_Kt, remove_cvref_t<_Container>>;

And similarly for __heterogeneous_tree_key in stl_tree.h.

> Also do you need remove_cvref_t on _Container?

It is not needed for use in the container member functions, as
there the container type is provided explicitly, but when the
container is an argument, deduction adds a (spurious) ref.
The concept might end up used beyond just these headers.
But that anyway doesn't need the cv part.

>     +#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 ff3f8f4c6e7..2ee61e79f02 100644
>     --- a/libstdc++-v3/include/bits/stl_function.h
>     +++ b/libstdc++-v3/include/bits/stl_function.h
>     @@ -1486,6 +1486,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>       #endif
>       #endif
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +template <typename _Kt, typename _Container>
>     +  concept __heterogeneous_key =
>     +    (!is_convertible_v<_Kt&&, typename _Container::iterator> &&
>     +     !is_convertible_v<_Kt&&, typename _Container::const_iterator> &&
>     +     !is_same_v<remove_cv_t<typename _Container::key_type>,
>     +               remove_cvref_t<_Kt>>);
> 
> I do not think you need remove_cv_t on key_type here.

Not for our containers, but maybe for others.

>     +#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 62d66cef6b2..69d57df728a 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 <bits/stl_tree.h>
> 
>       namespace std _GLIBCXX_VISIBILITY(default)
>       {
>     @@ -679,6 +680,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             extract(const key_type& __x)
>             { return _M_t.extract(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      node_type
>     +      extract(__heterogeneous_tree_key<map> auto&& __key)
> 
> I would suggest that you use an normal-template head here, as it gives 
> more readable error message,
> for illustration see here https://godbolt.org/z/3rd4KoTE7 <https:// 
> godbolt.org/z/3rd4KoTE7>:
> 
> <source>:7:10: note: candidate template ignored: constraints not 
> satisfied [with U = int]
>      7 |     void usingRequires(U&&) {}
> 
> <source>:9:10: note: candidate template ignored: constraints not 
> satisfied [with x:auto = int]
>      9 |     void usingAuto(Check auto&& x) {}
> 
> The [x::auto = int] is much more confusing, than just U&&. This apply 
> everywhere.

Anyway declaring it like

   template <__heterogeneous_tree_key<map> _Kt>
     node_type
     extract(_Kt&& __key)

avoids that problem, without a separate "requires" line.

>     +      { return _M_t._M_extract_tr(__key); }
>     +#endif
>     +
>             /// Re-insert an extracted node.
>             insert_return_type
>             insert(node_type&& __nh)
>     @@ -1158,6 +1165,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             erase(const key_type& __x)
>             { return _M_t._M_erase_unique(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      size_type
>     +      erase(__heterogeneous_tree_key<map> auto&& __key)
>     +      { return _M_t._M_erase_unique_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_multimap.h b/libstdc++-
>     v3/include/bits/stl_multimap.h
>     index b2ae2bae745..4ba628ac7b4 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>
> 
>       namespace std _GLIBCXX_VISIBILITY(default)
>       {
>     @@ -688,6 +689,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             extract(const key_type& __x)
>             { return _M_t.extract(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      node_type
>     +      extract(__heterogeneous_tree_key<multimap> auto&& __key)
>     +      { return _M_t._M_extract_tr(__key); }
>     +#endif
>     +
>             /// Re-insert an extracted node.
>             iterator
>             insert(node_type&& __nh)
>     @@ -787,6 +794,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             erase(const key_type& __x)
>             { return _M_t.erase(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      size_type
>     +      erase(__heterogeneous_tree_key<multimap> auto&& __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 b6e1bfc4246..49d12884bdf 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>
> 
>       namespace std _GLIBCXX_VISIBILITY(default)
>       {
>     @@ -621,6 +622,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             extract(const key_type& __x)
>             { return _M_t.extract(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      node_type
>     +      extract(__heterogeneous_tree_key<multiset> auto&& __key)
>     +      { return _M_t._M_extract_tr(__key); }
>     +#endif
>     +
>             /// Re-insert an extracted node.
>             iterator
>             insert(node_type&& __nh)
>     @@ -712,6 +719,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             erase(const key_type& __x)
>             { return _M_t.erase(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      size_type
>     +      erase(__heterogeneous_tree_key<multiset> auto&& __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 f03d9e54d33..8d675040bb7 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>
> 
>       namespace std _GLIBCXX_VISIBILITY(default)
>       {
>     @@ -639,6 +640,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             extract(const key_type& __x)
>             { return _M_t.extract(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      node_type
>     +      extract(__heterogeneous_tree_key<set> auto&& __key)
> 
>     +      { return _M_t._M_extract_tr(__key); }
>     +#endif
>     +
>             /// Re-insert an extracted node.
>             insert_return_type
>             insert(node_type&& __nh)
>     @@ -730,6 +737,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             erase(const key_type& __x)
>             { return _M_t._M_erase_unique(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      size_type
>     +      erase(__heterogeneous_tree_key<set> auto&& __key)
>     +      { return _M_t._M_erase_unique_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 e78fa1dbfb3..5e922e426c6 100644
>     --- a/libstdc++-v3/include/bits/stl_tree.h
>     +++ b/libstdc++-v3/include/bits/stl_tree.h
>     @@ -1399,8 +1399,8 @@ namespace __rb_tree
>       #if __cplusplus >= 201103L
>                // Enforce this here with a user-friendly message.
>                static_assert(
>     -           __is_invocable<const _Compare&, const _Key&, const
>     _Key&>::value,
>     -           "comparison object must be invocable with two arguments
>     of key type"
>     +           __is_invocable<const _Compare&, const _Key1&, const
>     _Key2&>::value,
>     +           "comparison object must be invocable with key types used"
>                );
>       #endif
>                return _M_impl._M_key_compare(__k1, __k2);
>     @@ -1539,10 +1539,24 @@ namespace __rb_tree
>             _M_lower_bound(_Base_ptr __x, _Base_ptr __y,
>                           const _Key& __k) const;
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      template <typename _HetKey>
>     +       _Base_ptr
>     +       _M_lower_bound_tr(
>     +         _Base_ptr __x, _Base_ptr __y, const _HetKey& __k) const;
>     +#endif
>     +
>             _Base_ptr
>             _M_upper_bound(_Base_ptr __x, _Base_ptr __y,
>                           const _Key& __k) const;
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      template <typename _HetKey>
>     +       _Base_ptr
>     +       _M_upper_bound_tr(
>     +         _Base_ptr __x, _Base_ptr __y, const _HetKey& __k) const;
>     +#endif
>     +
>           public:
>             // allocation/deallocation
>       #if __cplusplus < 201103L
>     @@ -1850,9 +1864,21 @@ namespace __rb_tree
>             size_type
>             erase(const key_type& __x);
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      template <typename _HetKey>
>     +       size_type
>     +       _M_erase_tr(const _HetKey& __x);
>     +#endif
>     +
>             size_type
>             _M_erase_unique(const key_type& __x);
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      template<typename _HetKey>
>     +       size_type
>     +       _M_erase_unique_tr(const _HetKey& __x);
>     +#endif
>     +
>       #if __cplusplus >= 201103L
>             // _GLIBCXX_RESOLVE_LIB_DEFECTS
>             // DR 130. Associative erase should return an iterator.
>     @@ -2198,6 +2224,19 @@ namespace __rb_tree
>              return __nh;
>             }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      template <typename _HetKey>
>     +       node_type
>     +       _M_extract_tr(const _HetKey& __k)
>     +       {
>     +         node_type __nh;
>     +         auto __pos = _M_find_tr(__k);
>     +         if (__pos != end())
>     +           __nh = extract(const_iterator(__pos));
>     +         return __nh;
>     +       }
>     +#endif
>     +
>             template<typename _Compare2>
>              using _Compatible_tree
>                = _Rb_tree<_Key, _Val, _KeyOfValue, _Compare2, _Alloc>;
>     @@ -2612,6 +2651,24 @@ namespace __rb_tree
>             return __y;
>           }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +  template<typename _Key, typename _Val, typename _KeyOfValue,
>     +       typename _Compare, typename _Alloc>
>     +    template <typename _HetKey>
>     +      auto
>     +      _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
>     +      _M_lower_bound_tr(_Base_ptr __x, _Base_ptr __y, const
>     _HetKey& __k) const
>     +       -> _Base_ptr
>     +      {
>     +       while (__x)
>     +         if (!_M_key_compare(_S_key(__x), __k))
>     +           __y = __x, __x = _S_left(__x);
>     +         else
>     +           __x = _S_right(__x);
>     +       return __y;
>     +      }
>     +#endif
>     +
>         template<typename _Key, typename _Val, typename _KeyOfValue,
>                 typename _Compare, typename _Alloc>
>           typename _Rb_tree<_Key, _Val, _KeyOfValue,
>     @@ -2628,6 +2685,24 @@ namespace __rb_tree
>             return __y;
>           }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +  template<typename _Key, typename _Val, typename _KeyOfValue,
>     +          typename _Compare, typename _Alloc>
>     +    template <typename _HetKey>
>     +      auto
>     +      _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
>     +      _M_upper_bound_tr(_Base_ptr __x, _Base_ptr __y, const
>     _HetKey& __k) const
>     +       -> _Base_ptr
>     +      {
>     +       while (__x)
>     +         if (_M_key_compare(__k, _S_key(__x)))
>     +           __y = __x, __x = _S_left(__x);
>     +         else
>     +           __x = _S_right(__x);
>     +       return __y;
>     +      }
>     +#endif
>     +
>         template<typename _Key, typename _Val, typename _KeyOfValue,
>                 typename _Compare, typename _Alloc>
>           pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
>     @@ -3142,6 +3217,22 @@ namespace __rb_tree
>             return __old_size - size();
>           }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +  template<typename _Key, typename _Val, typename _KeyOfValue,
>     +          typename _Compare, typename _Alloc>
>     +    template <typename _HetKey>
>     +      auto
>     +      _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
>     +      _M_erase_tr(const _HetKey& __x)
>     +       -> size_type
>     +      {
>     +       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();
>     +      }
>     +#endif
>     +
>         template<typename _Key, typename _Val, typename _KeyOfValue,
>                 typename _Compare, typename _Alloc>
>           typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare,
>     _Alloc>::size_type
>     @@ -3156,6 +3247,24 @@ namespace __rb_tree
>             return 1;
>           }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +  template<typename _Key, typename _Val, typename _KeyOfValue,
>     +       typename _Compare, typename _Alloc>
>     +    template<typename _HetKey>
>     +      auto
>     +      _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
>     +      _M_erase_unique_tr(const _HetKey& __x)
> 
> Again, similar comment, we need to erase all equivalent elements.
> We can forward inside this function to _M_erase_tr for the moment, and 
> later see if we can get optimized
> for one element case.

Right. I don't think trees have the problem that hash
tables have with possibly two or more discontiguous ranges.

>     +       -> size_type
>     +      {
>     +       iterator __it = _M_find_tr(__x);
>     +       if (__it == end())
>     +         return 0;
>     +
>     +       _M_erase_aux(__it);
>     +       return 1;
>     +      }
>     +#endif
>     +
>         template<typename _Key, typename _Val, typename _KeyOfValue,
>                 typename _Compare, typename _Alloc>
>           typename _Rb_tree<_Key, _Val, _KeyOfValue,
>     @@ -3249,6 +3358,13 @@ namespace __rb_tree
>           };
>       #endif // C++17
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +template <typename _Kt, typename _Container>
>     +  concept __heterogeneous_tree_key =
>     +    __heterogeneous_key<_Kt, remove_cvref_t<_Container>> &&
>     +    __transparent_comparator<typename
>     remove_cvref_t<_Container>::key_compare>;
> 
> Put __transparent_comparation before  __heterogeneous_key, for same 
> reason as above.
> 
>     +#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 b9b2772aaa9..51ad049c2d1 100644
>     --- a/libstdc++-v3/include/bits/unordered_map.h
>     +++ b/libstdc++-v3/include/bits/unordered_map.h
>     @@ -501,6 +501,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             extract(const key_type& __key)
>             { return _M_h.extract(__key); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      node_type
>     +      extract(__heterogeneous_hash_key<unordered_map> auto&& __key)
>     +      { return _M_h._M_extract_tr(__key); }
>     +#endif
>     +
>             /// Re-insert an extracted node.
>             insert_return_type
>             insert(node_type&& __nh)
>     @@ -848,6 +854,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             erase(const key_type& __x)
>             { return _M_h.erase(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      size_type
>     +      erase(__heterogeneous_hash_key<unordered_map> auto&& __key)
>     +      { return _M_h._M_erase_tr(__key); }
>     +#endif
>     +
>             /**
>              *  @brief Erases a [__first,__last) range of elements from an
>              *  %unordered_map.
>     @@ -1872,6 +1884,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             extract(const key_type& __key)
>             { return _M_h.extract(__key); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      node_type
>     +      extract(__heterogeneous_hash_key<unordered_multimap> auto&&
>     __key)
>     +      { return _M_h._M_extract_tr(__key); }
>     +#endif
>     +
>             /// Re-insert an extracted node.
>             iterator
>             insert(node_type&& __nh)
>     @@ -1922,6 +1940,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             erase(const key_type& __x)
>             { return _M_h.erase(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      size_type
>     +      erase(__heterogeneous_hash_key<unordered_multimap> auto&& __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 29bc49a590a..b2c539ebbaa 100644
>     --- a/libstdc++-v3/include/bits/unordered_set.h
>     +++ b/libstdc++-v3/include/bits/unordered_set.h
>     @@ -581,6 +581,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             extract(const key_type& __key)
>             { return _M_h.extract(__key); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      node_type
>     +      extract(__heterogeneous_hash_key<unordered_set> auto&& __key)
>     +      { return _M_h._M_extract_tr(__key); }
>     +#endif
>     +
>             /// Re-insert an extracted node.
>             insert_return_type
>             insert(node_type&& __nh)
>     @@ -632,6 +638,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             erase(const key_type& __x)
>             { return _M_h.erase(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      size_type
>     +      erase(__heterogeneous_hash_key<unordered_set> auto&& __key)
>     +      { return _M_h._M_erase_tr(__key); }
>     +#endif
>     +
>             /**
>              *  @brief Erases a [__first,__last) range of elements from an
>              *  %unordered_set.
>     @@ -1574,6 +1586,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             extract(const key_type& __key)
>             { return _M_h.extract(__key); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      node_type
>     +      extract(__heterogeneous_hash_key<unordered_multiset> auto&&
>     __key)
>     +      { return _M_h._M_extract_tr(__key); }
>     +#endif
>     +
>             /// Re-insert an extracted node.
>             iterator
>             insert(node_type&& __nh)
>     @@ -1627,6 +1645,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
>             erase(const key_type& __x)
>             { return _M_h.erase(__x); }
> 
>     +#if __glibcxx_associative_heterogeneous_erasure // C++23, P2077
>     +      size_type
>     +      erase(__heterogeneous_hash_key<unordered_multiset> auto&& __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 04232187965..e3b9627b022 100644
>     --- a/libstdc++-v3/include/bits/version.def
>     +++ b/libstdc++-v3/include/bits/version.def
>     @@ -1589,6 +1589,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 df7a291b05f..39f5d462550 100644
>     --- a/libstdc++-v3/include/bits/version.h
>     +++ b/libstdc++-v3/include/bits/version.h
>     @@ -1756,6 +1756,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
>     @@ -2198,7 +2208,7 @@
>       #   define __cpp_lib_observable_checkpoint 202506L
>       #  endif
>       # endif
>     -#endif /* !defined(__cpp_lib_observable_checkpoint) &&
>     defined(__glibcxx_want_observable_checkpoint) */
>     +#endif /* !defined(__cpp_lib_observable_checkpoint) */
>       #undef __glibcxx_want_observable_checkpoint
> 
>       #if !defined(__cpp_lib_algorithm_default_value_type)
>     diff --git a/libstdc++-v3/include/std/map b/libstdc++-v3/include/std/map
>     index 6bfb53848ba..efdf915f081 100644
>     --- a/libstdc++-v3/include/std/map
>     +++ b/libstdc++-v3/include/std/map
>     @@ -59,9 +59,19 @@
>       #pragma GCC system_header
>       #endif
> 
>     +#define __glibcxx_want_allocator_traits_is_always_equal
>     +#define __glibcxx_want_containers_ranges
>     +#define __glibcxx_want_erase_if
>     +#define __glibcxx_want_generic_associative_lookup
>     +#define __glibcxx_want_map_try_emplace
>     +#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>
>     +
>       #include <bits/requires_hosted.h> // containers
> 
>     -#include <bits/stl_tree.h>
>       #include <bits/stl_map.h>
>       #include <bits/stl_multimap.h>
>       #include <bits/range_access.h>
>     @@ -71,16 +81,6 @@
>       # include <debug/map>
>       #endif
> 
>     -#define __glibcxx_want_allocator_traits_is_always_equal
>     -#define __glibcxx_want_containers_ranges
>     -#define __glibcxx_want_erase_if
>     -#define __glibcxx_want_generic_associative_lookup
>     -#define __glibcxx_want_map_try_emplace
>     -#define __glibcxx_want_node_extract
>     -#define __glibcxx_want_nonmember_container_access
>     -#define __glibcxx_want_tuple_like
>     -#include <bits/version.h>
>     -
>       #if __cplusplus >= 201703L
>       #include <bits/memory_resource.h>
>       namespace std _GLIBCXX_VISIBILITY(default)
>     diff --git a/libstdc++-v3/include/std/set b/libstdc++-v3/include/std/set
>     index cf7057aa6c6..0e6fb3e88c6 100644
>     --- a/libstdc++-v3/include/std/set
>     +++ b/libstdc++-v3/include/std/set
>     @@ -59,9 +59,18 @@
>       #pragma GCC system_header
>       #endif
> 
>     +#define __glibcxx_want_allocator_traits_is_always_equal
>     +#define __glibcxx_want_containers_ranges
>     +#define __glibcxx_want_erase_if
>     +#define __glibcxx_want_generic_associative_lookup
>     +#define __glibcxx_want_node_extract
>     +#define __glibcxx_want_nonmember_container_access
>     +#define __glibcxx_want_associative_heterogeneous_erasure
>     +#define __glibcxx_want_associative_heterogeneous_insertion
>     +#include <bits/version.h>
>     +
>       #include <bits/requires_hosted.h> // containers
> 
>     -#include <bits/stl_tree.h>
>       #include <bits/stl_set.h>
>       #include <bits/stl_multiset.h>
>       #include <bits/range_access.h>
>     @@ -71,14 +80,6 @@
>       # include <debug/set>
>       #endif
> 
>     -#define __glibcxx_want_allocator_traits_is_always_equal
>     -#define __glibcxx_want_containers_ranges
>     -#define __glibcxx_want_erase_if
>     -#define __glibcxx_want_generic_associative_lookup
>     -#define __glibcxx_want_node_extract
>     -#define __glibcxx_want_nonmember_container_access
>     -#include <bits/version.h>
>     -
>       #if __cplusplus >= 201703L
>       #include <bits/memory_resource.h>
>       namespace std _GLIBCXX_VISIBILITY(default)
>     diff --git a/libstdc++-v3/include/std/unordered_map b/libstdc++-v3/
>     include/std/unordered_map
>     index 3ae25d758ac..783ead99a4b 100644
>     --- a/libstdc++-v3/include/std/unordered_map
>     +++ b/libstdc++-v3/include/std/unordered_map
>     @@ -39,15 +39,6 @@
>       # include <bits/c++0x_warning.h>
>       #else
> 
>     -#include <initializer_list>
>     -#include <bits/unordered_map.h>
>     -#include <bits/range_access.h>
>     -#include <bits/erase_if.h>
>     -
>     -#ifdef _GLIBCXX_DEBUG
>     -# include <debug/unordered_map>
>     -#endif
>     -
>       #define __glibcxx_want_allocator_traits_is_always_equal
>       #define __glibcxx_want_containers_ranges
>       #define __glibcxx_want_erase_if
>     @@ -56,8 +47,18 @@
>       #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>
> 
>     +#include <initializer_list>
>     +#include <bits/unordered_map.h>
>     +#include <bits/range_access.h>
>     +#include <bits/erase_if.h>
>     +
>     +#ifdef _GLIBCXX_DEBUG
>     +# include <debug/unordered_map>
>     +#endif
>     +
>       #if __cplusplus >= 201703L
>       #include <bits/memory_resource.h>
>       namespace std _GLIBCXX_VISIBILITY(default)
>     diff --git a/libstdc++-v3/include/std/unordered_set b/libstdc++-v3/
>     include/std/unordered_set
>     index b561163d31d..9dcecb050b0 100644
>     --- a/libstdc++-v3/include/std/unordered_set
>     +++ b/libstdc++-v3/include/std/unordered_set
>     @@ -39,6 +39,15 @@
>       # include <bits/c++0x_warning.h>
>       #else
> 
>     +#define __glibcxx_want_allocator_traits_is_always_equal
>     +#define __glibcxx_want_containers_ranges
>     +#define __glibcxx_want_erase_if
>     +#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>
>     +
>       #include <initializer_list>
>       #include <bits/unordered_set.h>
>       #include <bits/range_access.h>
>     @@ -48,14 +57,6 @@
>       # include <debug/unordered_set>
>       #endif
> 
>     -#define __glibcxx_want_allocator_traits_is_always_equal
>     -#define __glibcxx_want_containers_ranges
>     -#define __glibcxx_want_erase_if
>     -#define __glibcxx_want_generic_unordered_lookup
>     -#define __glibcxx_want_node_extract
>     -#define __glibcxx_want_nonmember_container_access
>     -#include <bits/version.h>
>     -
>       #if __cplusplus >= 201703L
>       #include <bits/memory_resource.h>
>       namespace std _GLIBCXX_VISIBILITY(default)
>     diff --git a/libstdc++-v3/testsuite/23_containers/map/modifiers/
>     erase/p2077.cc b/libstdc++-v3/testsuite/23_containers/map/modifiers/
>     erase/p2077.cc
>     new file mode 100644
>     index 00000000000..b64fe8abf82
>     --- /dev/null
>     +++ b/libstdc++-v3/testsuite/23_containers/map/modifiers/erase/p2077.cc
>     @@ -0,0 +1,91 @@
>     +// Copyright (C) 2011-2025 Free Software Foundation, Inc.
>     +//
>     +// This file is part of the GNU ISO C++ Library.  This library is free
>     +// software; you can redistribute it and/or modify it under the
>     +// terms of the GNU General Public License as published by the
>     +// Free Software Foundation; either version 3, or (at your option)
>     +// any later version.
>     +//
>     +// This library is distributed in the hope that it will be useful,
>     +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +// GNU General Public License for more details.
>     +//
>     +// You should have received a copy of the GNU General Public
>     License along
>     +// with this library; see the file COPYING3.  If not see
>     +// <http://www.gnu.org/licenses/ <http://www.gnu.org/licenses/>>.
>     +//
>     +
>     +// { dg-do compile { target c++23 } }
>     +
>     +#include <map>
>     +#include <string>
>     +#include <string_view>
>     +#include <utility>
>     +#include <testsuite_hooks.h>
>     +
>     +struct X {
>     +  std::string s;
>     +  friend bool operator<(X const& a, X const& b) { return a.s < b.s; }
>     +};
>     +
>     +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) { return a.s < b.s; }
>     +  friend bool operator<(X const& a, Y const& b) { return a.s < b.s; }
>     +  friend bool operator<(Y const& a, X 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);
>     +}
>     +
>     +int main()
>     +{
>     +  test1();
>     +  test2();
>     +  test3();
>     +  test4();
>     +}
>     diff --git a/libstdc++-v3/testsuite/23_containers/multimap/
>     modifiers/erase/p2077.cc b/libstdc++-v3/testsuite/23_containers/
>     multimap/modifiers/erase/p2077.cc
>     new file mode 100644
>     index 00000000000..3fb6a421337
>     --- /dev/null
>     +++ b/libstdc++-v3/testsuite/23_containers/multimap/modifiers/erase/
>     p2077.cc
>     @@ -0,0 +1,91 @@
>     +// Copyright (C) 2011-2025 Free Software Foundation, Inc.
>     +//
>     +// This file is part of the GNU ISO C++ Library.  This library is free
>     +// software; you can redistribute it and/or modify it under the
>     +// terms of the GNU General Public License as published by the
>     +// Free Software Foundation; either version 3, or (at your option)
>     +// any later version.
>     +//
>     +// This library is distributed in the hope that it will be useful,
>     +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +// GNU General Public License for more details.
>     +//
>     +// You should have received a copy of the GNU General Public
>     License along
>     +// with this library; see the file COPYING3.  If not see
>     +// <http://www.gnu.org/licenses/ <http://www.gnu.org/licenses/>>.
>     +//
>     +
>     +// { dg-do compile { target c++23 } }
>     +
>     +#include <map>
>     +#include <string>
>     +#include <string_view>
>     +#include <utility>
>     +#include <testsuite_hooks.h>
>     +
>     +struct X {
>     +  std::string s;
>     +  friend bool operator<(X const& a, X const& b) { return a.s < b.s; }
>     +};
>     +
>     +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) { return a.s < b.s; }
>     +  friend bool operator<(X const& a, Y const& b) { return a.s < b.s; }
>     +  friend bool operator<(Y const& a, X 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);
>     +}
>     +
>     +int main()
>     +{
>     +  test1();
>     +  test2();
>     +  test3();
>     +  test4();
>     +}
>     diff --git a/libstdc++-v3/testsuite/23_containers/multiset/
>     modifiers/erase/p2077.cc b/libstdc++-v3/testsuite/23_containers/
>     multiset/modifiers/erase/p2077.cc
>     new file mode 100644
>     index 00000000000..10a3cc07b2d
>     --- /dev/null
>     +++ b/libstdc++-v3/testsuite/23_containers/multiset/modifiers/erase/
>     p2077.cc
>     @@ -0,0 +1,85 @@
>     +// Copyright (C) 2011-2025 Free Software Foundation, Inc.
>     +//
>     +// This file is part of the GNU ISO C++ Library.  This library is free
>     +// software; you can redistribute it and/or modify it under the
>     +// terms of the GNU General Public License as published by the
>     +// Free Software Foundation; either version 3, or (at your option)
>     +// any later version.
>     +//
>     +// This library is distributed in the hope that it will be useful,
>     +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +// GNU General Public License for more details.
>     +//
>     +// You should have received a copy of the GNU General Public
>     License along
>     +// with this library; see the file COPYING3.  If not see
>     +// <http://www.gnu.org/licenses/ <http://www.gnu.org/licenses/>>.
>     +//
>     +
>     +// { dg-do compile { target c++23 } }
>     +
>     +#include <set>
>     +#include <string>
>     +#include <string_view>
>     +#include <utility>
>     +#include <testsuite_hooks.h>
>     +
>     +struct X {
>     +  std::string s;
>     +  friend bool operator<(X const& a, X const& b) { return a.s < b.s; }
>     +};
>     +
>     +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) { return a.s < b.s; }
>     +  friend bool operator<(X const& a, Y const& b) { return a.s < b.s; }
>     +  friend bool operator<(Y const& a, X 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);
>     +}
>     +
>     +int main()
>     +{
>     +  test1();
>     +  test2();
>     +  test3();
>     +  test4();
>     +}
>     diff --git a/libstdc++-v3/testsuite/23_containers/set/modifiers/
>     erase/p2077.cc b/libstdc++-v3/testsuite/23_containers/set/modifiers/
>     erase/p2077.cc
>     new file mode 100644
>     index 00000000000..3de72a66540
>     --- /dev/null
>     +++ b/libstdc++-v3/testsuite/23_containers/set/modifiers/erase/p2077.cc
>     @@ -0,0 +1,85 @@
>     +// Copyright (C) 2011-2025 Free Software Foundation, Inc.
>     +//
>     +// This file is part of the GNU ISO C++ Library.  This library is free
>     +// software; you can redistribute it and/or modify it under the
>     +// terms of the GNU General Public License as published by the
>     +// Free Software Foundation; either version 3, or (at your option)
>     +// any later version.
>     +//
>     +// This library is distributed in the hope that it will be useful,
>     +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +// GNU General Public License for more details.
>     +//
>     +// You should have received a copy of the GNU General Public
>     License along
>     +// with this library; see the file COPYING3.  If not see
>     +// <http://www.gnu.org/licenses/ <http://www.gnu.org/licenses/>>.
>     +//
>     +
>     +// { dg-do compile { target c++23 } }
>     +
>     +#include <set>
>     +#include <string>
>     +#include <string_view>
>     +#include <utility>
>     +#include <testsuite_hooks.h>
>     +
>     +struct X {
>     +  std::string s;
>     +  friend bool operator<(X const& a, X const& b) { return a.s < b.s; }
>     +};
>     +
>     +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) { return a.s < b.s; }
>     +  friend bool operator<(X const& a, Y const& b) { return a.s < b.s; }
>     +  friend bool operator<(Y const& a, X 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);
>     +}
>     +
>     +int main()
>     +{
>     +  test1();
>     +  test2();
>     +  test3();
>     +  test4();
>     +}
>     diff --git a/libstdc++-v3/testsuite/23_containers/unordered_map/
>     modifiers/p2077.cc b/libstdc++-v3/testsuite/23_containers/
>     unordered_map/modifiers/p2077.cc
>     new file mode 100644
>     index 00000000000..a22eb993b2b
>     --- /dev/null
>     +++ b/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/
>     p2077.cc
>     @@ -0,0 +1,94 @@
>     +// Copyright (C) 2011-2025 Free Software Foundation, Inc.
>     +//
>     +// This file is part of the GNU ISO C++ Library.  This library is free
>     +// software; you can redistribute it and/or modify it under the
>     +// terms of the GNU General Public License as published by the
>     +// Free Software Foundation; either version 3, or (at your option)
>     +// any later version.
>     +//
>     +// This library is distributed in the hope that it will be useful,
>     +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +// GNU General Public License for more details.
>     +//
>     +// You should have received a copy of the GNU General Public
>     License along
>     +// with this library; see the file COPYING3.  If not see
>     +// <http://www.gnu.org/licenses/ <http://www.gnu.org/licenses/>>.
>     +//
>     +
>     +// { dg-do compile { 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) { return a.s == b.s; }
>     +};
>     +
>     +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) { return a.s == b.s; }
>     +  friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
>     +  friend bool operator==(Y const& a, X 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/p2077.cc b/libstdc++-v3/
>     testsuite/23_containers/unordered_multimap/modifiers/p2077.cc
>     new file mode 100644
>     index 00000000000..3a455b2d701
>     --- /dev/null
>     +++ b/libstdc++-v3/testsuite/23_containers/unordered_multimap/
>     modifiers/p2077.cc
>     @@ -0,0 +1,94 @@
>     +// Copyright (C) 2011-2025 Free Software Foundation, Inc.
>     +//
>     +// This file is part of the GNU ISO C++ Library.  This library is free
>     +// software; you can redistribute it and/or modify it under the
>     +// terms of the GNU General Public License as published by the
>     +// Free Software Foundation; either version 3, or (at your option)
>     +// any later version.
>     +//
>     +// This library is distributed in the hope that it will be useful,
>     +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +// GNU General Public License for more details.
>     +//
>     +// You should have received a copy of the GNU General Public
>     License along
>     +// with this library; see the file COPYING3.  If not see
>     +// <http://www.gnu.org/licenses/ <http://www.gnu.org/licenses/>>.
>     +//
>     +
>     +// { dg-do compile { 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) { return a.s == b.s; }
>     +};
>     +
>     +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) { return a.s == b.s; }
>     +  friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
>     +  friend bool operator==(Y const& a, X 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/p2077.cc b/libstdc++-v3/
>     testsuite/23_containers/unordered_multiset/modifiers/p2077.cc
>     new file mode 100644
>     index 00000000000..671ba8c173e
>     --- /dev/null
>     +++ b/libstdc++-v3/testsuite/23_containers/unordered_multiset/
>     modifiers/p2077.cc
>     @@ -0,0 +1,92 @@
>     +// Copyright (C) 2011-2025 Free Software Foundation, Inc.
>     +//
>     +// This file is part of the GNU ISO C++ Library.  This library is free
>     +// software; you can redistribute it and/or modify it under the
>     +// terms of the GNU General Public License as published by the
>     +// Free Software Foundation; either version 3, or (at your option)
>     +// any later version.
>     +//
>     +// This library is distributed in the hope that it will be useful,
>     +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +// GNU General Public License for more details.
>     +//
>     +// You should have received a copy of the GNU General Public
>     License along
>     +// with this library; see the file COPYING3.  If not see
>     +// <http://www.gnu.org/licenses/ <http://www.gnu.org/licenses/>>.
>     +//
>     +
>     +// { dg-do compile { 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) { return a.s == b.s; }
>     +};
>     +
>     +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) { return a.s == b.s; }
>     +  friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
>     +  friend bool operator==(Y const& a, X 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/p2077.cc b/libstdc++-v3/testsuite/23_containers/
>     unordered_set/modifiers/p2077.cc
>     new file mode 100644
>     index 00000000000..b4f8dd0284c
>     --- /dev/null
>     +++ b/libstdc++-v3/testsuite/23_containers/unordered_set/modifiers/
>     p2077.cc
>     @@ -0,0 +1,92 @@
>     +// Copyright (C) 2011-2025 Free Software Foundation, Inc.
>     +//
>     +// This file is part of the GNU ISO C++ Library.  This library is free
>     +// software; you can redistribute it and/or modify it under the
>     +// terms of the GNU General Public License as published by the
>     +// Free Software Foundation; either version 3, or (at your option)
>     +// any later version.
>     +//
>     +// This library is distributed in the hope that it will be useful,
>     +// but WITHOUT ANY WARRANTY; without even the implied warranty of
>     +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>     +// GNU General Public License for more details.
>     +//
>     +// You should have received a copy of the GNU General Public
>     License along
>     +// with this library; see the file COPYING3.  If not see
>     +// <http://www.gnu.org/licenses/ <http://www.gnu.org/licenses/>>.
>     +//
>     +
>     +// { dg-do compile { 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) { return a.s == b.s; }
>     +};
>     +
>     +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) { return a.s == b.s; }
>     +  friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
>     +  friend bool operator==(Y const& a, X 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.50.1
> 



More information about the Libstdc++ mailing list