[PATCH] libstdc++: container erasure overloads (P2077) [PR117404]
Tomasz Kaminski
tkaminsk@redhat.com
Thu Nov 13 20:33:42 GMT 2025
On Thu, Nov 13, 2025 at 9:01 PM Nathan Myers <ncm@cantrip.org> wrote:
> 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.
>
I have linked the associative containers requirements, and it looks that
for unordered we do not allow the StringPrefix usage, in particular, we
have:
https://eel.is/c++draft/unord.req#general-10.21.3:
> if any two of eq(r1, kx), eq(r2, kx), and eq(r1, r2) are true, then all
three are true, and
So I think the original code is correct, including the if constexpr
(__unique_keys::value)
check.
The ordered containers on other side requires equivalent ranges to be
continuous,
so it is much simpler there.
>
> > 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.
>
If that happens, someone will add remove_cv_t as needed.
I am more on YAGNI here.
> 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.
>
This is the internal library name, user should not refer to it, so I see no
point
in paying extra template instantiations for use cases that may never
materialize.
>
> > +#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.
>
Yes, the above is fine.
>
> > + { 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
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20251113/647e4365/attachment-0001.htm>
More information about the Libstdc++
mailing list