Remaining to do:
* Add new declarations in debug headers too.
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.
libstdc++-v3/ChangeLog:
PR libstdc++/117404
* include/bits/version.def (associative_heterogeneous_erasure):
Define.
* include/bits/version.h: Regenerate.
* include/std/map: Request new feature from version.h.
* include/std/set: Same.
* include/std/unordered_map: Same.
* include/std/unordered_set: Same.
* include/bits/stl_map.h (extract, erase): Define overloads.
* include/bits/stl_set.h: Same.
* include/bits/stl_multimap.h: Same.
* include/bits/stl_multiset.h: Same.
* include/bits/unordered_map.h: Same, 2x.
* include/bits/unordered_set.h: Same, 2x.
* include/bits/stl_function.h (concepts __not_container_iterator,
__heterogeneous_key): Define.
* include/bits/hashtable.h (_M_find_before_node, _M_locate, extract):
Delegate to more-general _tr version.
(_M_find_before_node_tr, _M_locate_tr, _M_extract_tr, _M_erase_tr):
Add new members to support a heterogeneous key argument.
(_M_erase_some): Add new helper function.
(concept __heterogeneous_hash_key): Define.
* include/bits/stl_tree.h (_M_lower_bound_tr, _M_upper_bound_tr,
_M_erase_tr, _M_extract_tr): Add new members to support a
heterogeneous key argument.
(concept __heterogeneous_tree_key): Define.
* testsuite/23_containers/map/modifiers/hetero/erase.cc: New test.
* testsuite/23_containers/multimap/modifiers/hetero/erase.cc: Same.
* testsuite/23_containers/multiset/modifiers/hetero/erase.cc: Same.
* testsuite/23_containers/set/modifiers/hetero/erase.cc: Same.
* testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc: Same.
* testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc:
Same.
* testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc:
Same.
* testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc: Same.
---
libstdc++-v3/include/bits/hashtable.h | 241 +++++++++++-------
libstdc++-v3/include/bits/stl_function.h | 12 +
libstdc++-v3/include/bits/stl_map.h | 20 ++
libstdc++-v3/include/bits/stl_multimap.h | 15 ++
libstdc++-v3/include/bits/stl_multiset.h | 15 ++
libstdc++-v3/include/bits/stl_set.h | 18 ++
libstdc++-v3/include/bits/stl_tree.h | 75 +++++-
libstdc++-v3/include/bits/unordered_map.h | 28 ++
libstdc++-v3/include/bits/unordered_set.h | 28 ++
libstdc++-v3/include/bits/version.def | 8 +
libstdc++-v3/include/bits/version.h | 10 +
libstdc++-v3/include/std/map | 1 +
libstdc++-v3/include/std/set | 1 +
libstdc++-v3/include/std/unordered_map | 1 +
libstdc++-v3/include/std/unordered_set | 1 +
.../map/modifiers/hetero/erase.cc | 95 +++++++
.../multimap/modifiers/hetero/erase.cc | 95 +++++++
.../multiset/modifiers/hetero/erase.cc | 88 +++++++
.../set/modifiers/hetero/erase.cc | 88 +++++++
.../unordered_map/modifiers/hetero/erase.cc | 76 ++++++
.../modifiers/hetero/erase.cc | 75 ++++++
.../modifiers/hetero/erase.cc | 73 ++++++
.../unordered_set/modifiers/hetero/erase.cc | 73 ++++++
23 files changed, 1037 insertions(+), 100 deletions(-)
create mode 100644
libstdc++-v3/testsuite/23_containers/map/modifiers/hetero/erase.cc
create mode 100644
libstdc++-v3/testsuite/23_containers/multimap/modifiers/hetero/erase.cc
create mode 100644
libstdc++-v3/testsuite/23_containers/multiset/modifiers/hetero/erase.cc
create mode 100644
libstdc++-v3/testsuite/23_containers/set/modifiers/hetero/erase.cc
create mode 100644
libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc
create mode 100644
libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc
create mode 100644
libstdc++-v3/testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc
create mode 100644
libstdc++-v3/testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc
diff --git a/libstdc++-v3/include/bits/hashtable.h
b/libstdc++-v3/include/bits/hashtable.h
index c92b843bf0b..f7b8905d8ab 100644
--- a/libstdc++-v3/include/bits/hashtable.h
+++ b/libstdc++-v3/include/bits/hashtable.h
@@ -853,7 +853,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
// Find the node before the one matching the criteria.
__node_base_ptr
- _M_find_before_node(size_type, const key_type&, __hash_code) const;
+ _M_find_before_node(
+ size_type __bkt, const key_type& __k, __hash_code __code) const
+ { return _M_find_before_node_tr<key_type>(__bkt, __k, __code); }
template<typename _Kt>
__node_base_ptr
@@ -902,8 +904,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
// The _M_before pointer might point to _M_before_begin, so must not be
// cast to __node_ptr, and it must not be used to modify *_M_before
// except in non-const member functions, such as erase.
+
__location_type
- _M_locate(const key_type& __k) const;
+ _M_locate(const key_type& __k) const
+ { return _M_locate_tr<key_type>(__k); }
+
+ template <typename _Kt>
+ __location_type
+ _M_locate_tr(const _Kt& __k) const;
__node_ptr
_M_find_node(size_type __bkt, const key_type& __key,
@@ -1016,6 +1024,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
iterator
_M_erase(size_type __bkt, __node_base_ptr __prev_n, __node_ptr __n);
+ size_type
+ _M_erase_some(size_type __bkt, __node_base_ptr __prev_n, __node_ptr __n);
+
template<typename _InputIterator>
void
_M_insert_range_multi(_InputIterator __first, _InputIterator __last);
@@ -1163,6 +1174,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
size_type
erase(const key_type& __k);
+ template <typename _Kt>
+ size_type
+ _M_erase_tr(const _Kt& __k);
+
iterator
erase(const_iterator, const_iterator);
@@ -1274,14 +1289,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
/// Extract a node.
node_type
extract(const _Key& __k)
- {
- node_type __nh;
- __hash_code __code = this->_M_hash_code(__k);
- std::size_t __bkt = _M_bucket_index(__code);
- if (__node_base_ptr __prev_node = _M_find_before_node(__bkt, __k,
__code))
- __nh = _M_extract_node(__bkt, __prev_node);
- return __nh;
- }
+ { return _M_extract_tr<_Key>(__k); }
+
+ template <typename _Kt>
+ node_type
+ _M_extract_tr(const _Kt& __k)
+ {
+ node_type __nh;
+ __hash_code __code = this->_M_hash_code_tr(__k);
+ std::size_t __bkt = _M_bucket_index(__code);
+ if (__node_base_ptr __prev_node =
+ _M_find_before_node_tr(__bkt, __k, __code))
+ __nh = _M_extract_node(__bkt, __prev_node);
+ return __nh;
+ }
/// Merge from another container of the same type.
void
@@ -2194,35 +2215,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
// Find the node before the one whose key compares equal to k in the bucket
// bkt. Return nullptr if no node is found.
- template<typename _Key, typename _Value, typename _Alloc,
- typename _ExtractKey, typename _Equal,
- typename _Hash, typename _RangeHash, typename _Unused,
- typename _RehashPolicy, typename _Traits>
- auto
- _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
- _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
- _M_find_before_node(size_type __bkt, const key_type& __k,
- __hash_code __code) const
- -> __node_base_ptr
- {
- __node_base_ptr __prev_p = _M_buckets[__bkt];
- if (!__prev_p)
- return nullptr;
-
- for (__node_ptr __p = static_cast<__node_ptr>(__prev_p->_M_nxt);;
- __p = __p->_M_next())
- {
- if (this->_M_equals(__k, __code, *__p))
- return __prev_p;
-
- if (__builtin_expect (!__p->_M_nxt ||
_M_bucket_index(*__p->_M_next()) != __bkt, 0))
- break;
- __prev_p = __p;
- }
-
- return nullptr;
- }
-
template<typename _Key, typename _Value, typename _Alloc,
typename _ExtractKey, typename _Equal,
typename _Hash, typename _RangeHash, typename _Unused,
@@ -2245,7 +2237,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
if (this->_M_equals_tr(__k, __code, *__p))
return __prev_p;
- if (__builtin_expect (!__p->_M_nxt ||
_M_bucket_index(*__p->_M_next()) != __bkt, 0))
+ if (__builtin_expect (
+ !__p->_M_nxt || _M_bucket_index(*__p->_M_next()) != __bkt, 0))
break;
__prev_p = __p;
}
@@ -2257,37 +2250,38 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
typename _ExtractKey, typename _Equal,
typename _Hash, typename _RangeHash, typename _Unused,
typename _RehashPolicy, typename _Traits>
- inline auto
- _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
- _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
- _M_locate(const key_type& __k) const
- -> __location_type
- {
- __location_type __loc;
- const auto __size = size();
+ template <typename _Kt>
+ inline auto
+ _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
+ _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
+ _M_locate_tr(const _Kt& __k) const
+ -> __location_type
+ {
+ __location_type __loc;
+ const auto __size = size();
- if (__size <= __small_size_threshold())
- {
- __loc._M_before = pointer_traits<__node_base_ptr>::
- pointer_to(const_cast<__node_base&>(_M_before_begin));
- while (__loc._M_before->_M_nxt)
- {
- if (this->_M_key_equals(__k, *__loc._M_node()))
- return __loc;
- __loc._M_before = __loc._M_before->_M_nxt;
- }
- __loc._M_before = nullptr; // Didn't find it.
- }
+ if (__size <= __small_size_threshold())
+ {
+ __loc._M_before = pointer_traits<__node_base_ptr>::
+ pointer_to(const_cast<__node_base&>(_M_before_begin));
+ while (__loc._M_before->_M_nxt)
+ {
+ if (this->_M_key_equals_tr(__k, *__loc._M_node()))
+ return __loc;
+ __loc._M_before = __loc._M_before->_M_nxt;
+ }
+ __loc._M_before = nullptr; // Didn't find it.
+ }
- __loc._M_hash_code = this->_M_hash_code(__k);
- __loc._M_bucket_index = _M_bucket_index(__loc._M_hash_code);
+ __loc._M_hash_code = this->_M_hash_code_tr(__k);
+ __loc._M_bucket_index = _M_bucket_index(__loc._M_hash_code);
- if (__size > __small_size_threshold())
- __loc._M_before = _M_find_before_node(__loc._M_bucket_index, __k,
- __loc._M_hash_code);
+ if (__size > __small_size_threshold())
+ __loc._M_before = _M_find_before_node_tr(
+ __loc._M_bucket_index, __k, __loc._M_hash_code);
- return __loc;
- }
+ return __loc;
+ }
template<typename _Key, typename _Value, typename _Alloc,
typename _ExtractKey, typename _Equal,
@@ -2598,6 +2592,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
+
template<typename _Key, typename _Value, typename _Alloc,
typename _ExtractKey, typename _Equal,
typename _Hash, typename _RangeHash, typename _Unused,
@@ -2617,47 +2612,87 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
auto __bkt = __loc._M_bucket_index;
if (__bkt == size_type(-1))
__bkt = _M_bucket_index(*__n);
-
if constexpr (__unique_keys::value)
{
_M_erase(__bkt, __prev_n, __n);
return 1;
}
else
+ return _M_erase_some(__bkt, __prev_n, __n);
+ }
+
+ template<typename _Key, typename _Value, typename _Alloc,
+ typename _ExtractKey, typename _Equal,
+ typename _Hash, typename _RangeHash, typename _Unused,
+ typename _RehashPolicy, typename _Traits>
+ auto
+ _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
+ _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
+ _M_erase_some(size_type __bkt, __node_base_ptr __prev_n, __node_ptr __n)
+ -> size_type
+ {
+ // _GLIBCXX_RESOLVE_LIB_DEFECTS
+ // 526. Is it undefined if a function in the standard changes
+ // in parameters?
+ // We use one loop to find all matching nodes and another to
+ // deallocate them so that the key stays valid during the first loop.
+ // It might be invalidated indirectly when destroying nodes.
+ __node_ptr __n_last = __n->_M_next();
+ while (__n_last && this->_M_node_equals(*__n, *__n_last))
+ __n_last = __n_last->_M_next();
+
+ std::size_t __n_last_bkt
+ = __n_last ? _M_bucket_index(*__n_last) : __bkt;
+
+ // Deallocate nodes.
+ size_type __result = 0;
+ do
{
- // _GLIBCXX_RESOLVE_LIB_DEFECTS
- // 526. Is it undefined if a function in the standard changes
- // in parameters?
- // We use one loop to find all matching nodes and another to
- // deallocate them so that the key stays valid during the first loop.
- // It might be invalidated indirectly when destroying nodes.
- __node_ptr __n_last = __n->_M_next();
- while (__n_last && this->_M_node_equals(*__n, *__n_last))
- __n_last = __n_last->_M_next();
-
- std::size_t __n_last_bkt
- = __n_last ? _M_bucket_index(*__n_last) : __bkt;
-
- // Deallocate nodes.
- size_type __result = 0;
- do
- {
- __node_ptr __p = __n->_M_next();
- this->_M_deallocate_node(__n);
- __n = __p;
- ++__result;
- }
- while (__n != __n_last);
-
- _M_element_count -= __result;
- if (__prev_n == _M_buckets[__bkt])
- _M_remove_bucket_begin(__bkt, __n_last, __n_last_bkt);
- else if (__n_last_bkt != __bkt)
- _M_buckets[__n_last_bkt] = __prev_n;
- __prev_n->_M_nxt = __n_last;
- return __result;
+ __node_ptr __p = __n->_M_next();
+ this->_M_deallocate_node(__n);
+ __n = __p;
+ ++__result;
}
+ while (__n != __n_last);
+
+ _M_element_count -= __result;
+ if (__prev_n == _M_buckets[__bkt])
+ _M_remove_bucket_begin(__bkt, __n_last, __n_last_bkt);
+ else if (__n_last_bkt != __bkt)
+ _M_buckets[__n_last_bkt] = __prev_n;
+ __prev_n->_M_nxt = __n_last;
+ return __result;
}
+
+ template<typename _Key, typename _Value, typename _Alloc,
+ typename _ExtractKey, typename _Equal,
+ typename _Hash, typename _RangeHash, typename _Unused,
+ typename _RehashPolicy, typename _Traits>
+ template <typename _Kt>
+ auto
+ _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
+ _Hash, _RangeHash, _Unused, _RehashPolicy, _Traits>::
+ _M_erase_tr(const _Kt& __k)
+ -> size_type
+ {
+ auto __loc = _M_locate_tr(__k);
+ if (!__loc)
+ return 0;
+
+ __node_base_ptr __prev_n = __loc._M_before;
+ __node_ptr __n = __loc._M_node();
+ auto __bkt = __loc._M_bucket_index;
+ if (__bkt == size_type(-1))
+ __bkt = _M_bucket_index(*__n);
+ if constexpr (__unique_keys::value)
+ {
+ _M_erase(__bkt, __prev_n, __n);
+ return 1;
+ }
+ else
+ return _M_erase_some(__bkt, __prev_n, __n);
+ }
+
#pragma GCC diagnostic pop
template<typename _Key, typename _Value, typename _Alloc,
@@ -2977,6 +3012,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
= __enable_if_t<!__or_<is_integral<_Hash>,
__is_allocator<_Hash>>::value>;
#endif
+#ifdef __cpp_concepts
+template <typename _Kt, typename _Container>
+ concept __heterogeneous_hash_key =
+ __transparent_comparator<typename _Container::hasher> &&
+ __transparent_comparator<typename _Container::key_equal> &&
+ __heterogeneous_key<_Kt, _Container>;
+#endif
+
/// @endcond
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
diff --git a/libstdc++-v3/include/bits/stl_function.h
b/libstdc++-v3/include/bits/stl_function.h
index 0600de72b10..edbbfe11b96 100644
--- a/libstdc++-v3/include/bits/stl_function.h
+++ b/libstdc++-v3/include/bits/stl_function.h
@@ -1525,6 +1525,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
#endif
#endif
+#ifdef __cpp_concepts
+template <typename _Kt, typename _Container>
+ concept __not_container_iterator =
+ (!is_convertible_v<_Kt&&, typename _Container::iterator> &&
+ !is_convertible_v<_Kt&&, typename _Container::const_iterator>);
+
+template <typename _Kt, typename _Container>
+ concept __heterogeneous_key =
+ (!is_same_v<typename _Container::key_type, remove_cvref_t<_Kt>>) &&
+ __not_container_iterator<_Kt, _Container>;
+#endif
+
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
diff --git a/libstdc++-v3/include/bits/stl_map.h
b/libstdc++-v3/include/bits/stl_map.h
index 6620bfee931..4cb0c982c3d 100644
--- a/libstdc++-v3/include/bits/stl_map.h
+++ b/libstdc++-v3/include/bits/stl_map.h
@@ -65,6 +65,7 @@
#if __glibcxx_containers_ranges // C++ >= 23
# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
#endif
+// #include <stl_tree.h> // done in std/map
namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -679,6 +680,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
extract(const key_type& __x)
{ return _M_t.extract(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_tree_key<map> _Kt>
+ node_type
+ extract(_Kt&& __key)
+ { return _M_t._M_extract_tr(__key); }
+#endif
+
/// Re-insert an extracted node.
insert_return_type
insert(node_type&& __nh)
@@ -1143,6 +1151,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
{ _M_t.erase(__position); }
#endif
+ ///@{
/**
* @brief Erases elements according to the provided key.
* @param __x Key of element to be erased.
@@ -1158,6 +1167,17 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
erase(const key_type& __x)
{ return _M_t._M_erase_unique(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ // Note that for some types _Kt this may erase more than
+ // one element, such as if _Kt::operator< checks only part
+ // of the key.
+ template <__heterogeneous_tree_key<map> _Kt>
+ size_type
+ erase(_Kt&& __x)
+ { return _M_t._M_erase_tr(__x); }
+#endif
+ ///@}
+
#if __cplusplus >= 201103L
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 130. Associative erase should return an iterator.
diff --git a/libstdc++-v3/include/bits/stl_multimap.h
b/libstdc++-v3/include/bits/stl_multimap.h
index ea68401a88c..6f4e425d771 100644
--- a/libstdc++-v3/include/bits/stl_multimap.h
+++ b/libstdc++-v3/include/bits/stl_multimap.h
@@ -63,6 +63,7 @@
#if __glibcxx_containers_ranges // C++ >= 23
# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
#endif
+// #include <bits/stl_tree.h> // done in std/map
namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -688,6 +689,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
extract(const key_type& __x)
{ return _M_t.extract(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_tree_key<multimap> _Kt>
+ node_type
+ extract(_Kt&& __key)
+ { return _M_t._M_extract_tr(__key); }
+#endif
+
/// Re-insert an extracted node.
iterator
insert(node_type&& __nh)
@@ -787,6 +795,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
erase(const key_type& __x)
{ return _M_t.erase(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_tree_key<multimap> _Kt>
+ size_type
+ erase(_Kt&& __key)
+ { return _M_t._M_erase_tr(__key); }
+#endif
+
#if __cplusplus >= 201103L
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 130. Associative erase should return an iterator.
diff --git a/libstdc++-v3/include/bits/stl_multiset.h
b/libstdc++-v3/include/bits/stl_multiset.h
index db950da50ea..64047ad7631 100644
--- a/libstdc++-v3/include/bits/stl_multiset.h
+++ b/libstdc++-v3/include/bits/stl_multiset.h
@@ -63,6 +63,7 @@
#if __glibcxx_containers_ranges // C++ >= 23
# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
#endif
+// #include <bits/stl_tree.h> // done in std/set
namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -621,6 +622,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
extract(const key_type& __x)
{ return _M_t.extract(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_tree_key<multiset> _Kt>
+ node_type
+ extract(_Kt&& __key)
+ { return _M_t._M_extract_tr(__key); }
+#endif
+
/// Re-insert an extracted node.
iterator
insert(node_type&& __nh)
@@ -712,6 +720,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
erase(const key_type& __x)
{ return _M_t.erase(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_tree_key<multiset> _Kt>
+ size_type
+ erase(_Kt&& __key)
+ { return _M_t._M_erase_tr(__key); }
+#endif
+
#if __cplusplus >= 201103L
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 130. Associative erase should return an iterator.
diff --git a/libstdc++-v3/include/bits/stl_set.h
b/libstdc++-v3/include/bits/stl_set.h
index 96089fb172d..d3a4c089110 100644
--- a/libstdc++-v3/include/bits/stl_set.h
+++ b/libstdc++-v3/include/bits/stl_set.h
@@ -63,6 +63,7 @@
#if __glibcxx_containers_ranges // C++ >= 23
# include <bits/ranges_base.h> // ranges::begin, ranges::distance etc.
#endif
+// #include <bits/stl_tree.h> // done in std/set
namespace std _GLIBCXX_VISIBILITY(default)
{
@@ -639,6 +640,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
extract(const key_type& __x)
{ return _M_t.extract(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_tree_key<set> _Kt>
+ node_type
+ extract(_Kt&& __key)
+ { return _M_t._M_extract_tr(__key); }
+#endif
+
/// Re-insert an extracted node.
insert_return_type
insert(node_type&& __nh)
@@ -730,6 +738,16 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
erase(const key_type& __x)
{ return _M_t._M_erase_unique(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ // Note that for some types _Kt this may erase more than
+ // one element, such as if _Kt::operator< checks only part
+ // of the key.
+ template <__heterogeneous_tree_key<set> _Kt>
+ size_type
+ erase(_Kt&& __key)
+ { return _M_t._M_erase_tr(__key); }
+#endif
+
#if __cplusplus >= 201103L
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 130. Associative erase should return an iterator.
diff --git a/libstdc++-v3/include/bits/stl_tree.h
b/libstdc++-v3/include/bits/stl_tree.h
index 8f0aa731c93..7a733241ec5 100644
--- a/libstdc++-v3/include/bits/stl_tree.h
+++ b/libstdc++-v3/include/bits/stl_tree.h
@@ -1400,7 +1400,7 @@ namespace __rb_tree
// 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"
+ "comparison object must be invocable with arguments of key_type"
);
#endif
return _M_impl._M_key_compare(__k1, __k2);
@@ -1539,10 +1539,18 @@ namespace __rb_tree
_M_lower_bound(_Base_ptr __x, _Base_ptr __y,
const _Key& __k) const;
+ template <typename _Kt>
+ _Base_ptr
+ _M_lower_bound_tr(_Base_ptr __x, _Base_ptr __y, const _Kt& __k) const;
+
_Base_ptr
_M_upper_bound(_Base_ptr __x, _Base_ptr __y,
const _Key& __k) const;
+ template <typename _Kt>
+ _Base_ptr
+ _M_upper_bound_tr(_Base_ptr __x, _Base_ptr __y, const _Kt& __k) const;
+
public:
// allocation/deallocation
#if __cplusplus < 201103L
@@ -1850,6 +1858,10 @@ namespace __rb_tree
size_type
erase(const key_type& __x);
+ template <typename _Kt>
+ size_type
+ _M_erase_tr(const _Kt& __x);
+
size_type
_M_erase_unique(const key_type& __x);
@@ -2198,6 +2210,17 @@ namespace __rb_tree
return __nh;
}
+ template <typename _Kt>
+ node_type
+ _M_extract_tr(const _Kt& __k)
+ {
+ node_type __nh;
+ auto __pos = _M_find_tr(__k);
+ if (__pos != end())
+ __nh = extract(const_iterator(__pos));
+ return __nh;
+ }
+
template<typename _Compare2>
using _Compatible_tree
= _Rb_tree<_Key, _Val, _KeyOfValue, _Compare2, _Alloc>;
@@ -2612,6 +2635,21 @@ namespace __rb_tree
return __y;
}
+ template<typename _Key, typename _Val, typename _KeyOfValue,
+ typename _Compare, typename _Alloc>
+ template <typename _Kt>
+ typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr
+ _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
+ _M_lower_bound_tr(_Base_ptr __x, _Base_ptr __y, const _Kt& __k) const
+ {
+ while (__x)
+ if (!_M_key_compare(_S_key(__x), __k))
+ __y = __x, __x = _S_left(__x);
+ else
+ __x = _S_right(__x);
+ return __y;
+ }
+
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
typename _Rb_tree<_Key, _Val, _KeyOfValue,
@@ -2628,6 +2666,21 @@ namespace __rb_tree
return __y;
}
+ template<typename _Key, typename _Val, typename _KeyOfValue,
+ typename _Compare, typename _Alloc>
+ template <typename _Kt>
+ typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr
+ _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
+ _M_upper_bound_tr(_Base_ptr __x, _Base_ptr __y, const _Kt& __k) const
+ {
+ while (__x)
+ if (_M_key_compare(__k, _S_key(__x)))
+ __y = __x, __x = _S_left(__x);
+ else
+ __x = _S_right(__x);
+ return __y;
+ }
+
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
@@ -3142,6 +3195,19 @@ namespace __rb_tree
return __old_size - size();
}
+ template<typename _Key, typename _Val, typename _KeyOfValue,
+ typename _Compare, typename _Alloc>
+ template <typename _Kt>
+ typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::size_type
+ _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
+ _M_erase_tr(const _Kt& __x)
+ {
+ pair<iterator, iterator> __p = _M_equal_range_tr(__x);
+ const size_type __old_size = size();
+ _M_erase_aux(__p.first, __p.second);
+ return __old_size - size();
+ }
+
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::size_type
@@ -3249,6 +3315,13 @@ namespace __rb_tree
};
#endif // C++17
+#ifdef __cpp_concepts
+template <typename _Kt, typename _Container>
+ concept __heterogeneous_tree_key =
+ __transparent_comparator<typename _Container::key_compare> &&
+ __heterogeneous_key<_Kt, _Container>;
+#endif
+
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
diff --git a/libstdc++-v3/include/bits/unordered_map.h
b/libstdc++-v3/include/bits/unordered_map.h
index 36f5a8d91c3..9b74cba8675 100644
--- a/libstdc++-v3/include/bits/unordered_map.h
+++ b/libstdc++-v3/include/bits/unordered_map.h
@@ -501,6 +501,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
extract(const key_type& __key)
{ return _M_h.extract(__key); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_hash_key<unordered_map> _Kt>
+ node_type
+ extract(_Kt&& __key)
+ { return _M_h._M_extract_tr(__key); }
+#endif
+
/// Re-insert an extracted node.
insert_return_type
insert(node_type&& __nh)
@@ -848,6 +855,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
erase(const key_type& __x)
{ return _M_h.erase(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_hash_key<unordered_map> _Kt>
+ size_type
+ erase(_Kt&& __key)
+ { return _M_h._M_erase_tr(__key); }
+#endif
+
/**
* @brief Erases a [__first,__last) range of elements from an
* %unordered_map.
@@ -1872,6 +1886,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
extract(const key_type& __key)
{ return _M_h.extract(__key); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_hash_key<unordered_multimap> _Kt>
+ node_type
+ extract(_Kt&& __key)
+ { return _M_h._M_extract_tr(__key); }
+#endif
+
/// Re-insert an extracted node.
iterator
insert(node_type&& __nh)
@@ -1922,6 +1943,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
erase(const key_type& __x)
{ return _M_h.erase(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_hash_key<unordered_multimap> _Kt>
+ size_type
+ erase(_Kt&& __key)
+ { return _M_h._M_erase_tr(__key); }
+#endif
+
/**
* @brief Erases a [__first,__last) range of elements from an
* %unordered_multimap.
diff --git a/libstdc++-v3/include/bits/unordered_set.h
b/libstdc++-v3/include/bits/unordered_set.h
index 3a96229e1f5..22b2ad9caf4 100644
--- a/libstdc++-v3/include/bits/unordered_set.h
+++ b/libstdc++-v3/include/bits/unordered_set.h
@@ -581,6 +581,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
extract(const key_type& __key)
{ return _M_h.extract(__key); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_hash_key<unordered_set> _Kt>
+ node_type
+ extract(_Kt&& __key)
+ { return _M_h._M_extract_tr(__key); }
+#endif
+
/// Re-insert an extracted node.
insert_return_type
insert(node_type&& __nh)
@@ -632,6 +639,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
erase(const key_type& __x)
{ return _M_h.erase(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_hash_key<unordered_set> _Kt>
+ size_type
+ erase(_Kt&& __key)
+ { return _M_h._M_erase_tr(__key); }
+#endif
+
/**
* @brief Erases a [__first,__last) range of elements from an
* %unordered_set.
@@ -1574,6 +1588,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
extract(const key_type& __key)
{ return _M_h.extract(__key); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_hash_key<unordered_multiset> _Kt>
+ node_type
+ extract(_Kt&& __key)
+ { return _M_h._M_extract_tr(__key); }
+#endif
+
/// Re-insert an extracted node.
iterator
insert(node_type&& __nh)
@@ -1627,6 +1648,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
erase(const key_type& __x)
{ return _M_h.erase(__x); }
+#ifdef __glibcxx_associative_heterogeneous_erasure // C++23
+ template <__heterogeneous_hash_key<unordered_multiset> _Kt>
+ size_type
+ erase(_Kt&& __key)
+ { return _M_h._M_erase_tr(__key); }
+#endif
+
/**
* @brief Erases a [__first,__last) range of elements from an
* %unordered_multiset.
diff --git a/libstdc++-v3/include/bits/version.def
b/libstdc++-v3/include/bits/version.def
index ba0ef5aecfd..4b8e9d43ec2 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1633,6 +1633,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 74d1a28f75c..7602225cb6d 100644
--- a/libstdc++-v3/include/bits/version.h
+++ b/libstdc++-v3/include/bits/version.h
@@ -1801,6 +1801,16 @@
#endif /* !defined(__cpp_lib_invoke_r) */
#undef __glibcxx_want_invoke_r
+#if !defined(__cpp_lib_associative_heterogeneous_erasure)
+# if (__cplusplus >= 202100L)
+# define __glibcxx_associative_heterogeneous_erasure 202110L
+# if defined(__glibcxx_want_all) ||
defined(__glibcxx_want_associative_heterogeneous_erasure)
+# define __cpp_lib_associative_heterogeneous_erasure 202110L
+# endif
+# endif
+#endif /* !defined(__cpp_lib_associative_heterogeneous_erasure) */
+#undef __glibcxx_want_associative_heterogeneous_erasure
+
#if !defined(__cpp_lib_is_scoped_enum)
# if (__cplusplus >= 202100L)
# define __glibcxx_is_scoped_enum 202011L
diff --git a/libstdc++-v3/include/std/map b/libstdc++-v3/include/std/map
index 88e549411af..91612cf42c4 100644
--- a/libstdc++-v3/include/std/map
+++ b/libstdc++-v3/include/std/map
@@ -79,6 +79,7 @@
#define __glibcxx_want_node_extract
#define __glibcxx_want_nonmember_container_access
#define __glibcxx_want_tuple_like
+#define __glibcxx_want_associative_heterogeneous_erasure
#include <bits/version.h>
#if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/set b/libstdc++-v3/include/std/set
index af6d3814b4b..28eef1fc490 100644
--- a/libstdc++-v3/include/std/set
+++ b/libstdc++-v3/include/std/set
@@ -77,6 +77,7 @@
#define __glibcxx_want_generic_associative_lookup
#define __glibcxx_want_node_extract
#define __glibcxx_want_nonmember_container_access
+#define __glibcxx_want_associative_heterogeneous_erasure
#include <bits/version.h>
#if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/unordered_map
b/libstdc++-v3/include/std/unordered_map
index 9166f569ab1..f63be4104c5 100644
--- a/libstdc++-v3/include/std/unordered_map
+++ b/libstdc++-v3/include/std/unordered_map
@@ -56,6 +56,7 @@
#define __glibcxx_want_nonmember_container_access
#define __glibcxx_want_unordered_map_try_emplace
#define __glibcxx_want_tuple_like
+#define __glibcxx_want_associative_heterogeneous_erasure
#include <bits/version.h>
#if __cplusplus >= 201703L
diff --git a/libstdc++-v3/include/std/unordered_set
b/libstdc++-v3/include/std/unordered_set
index 157f72ce32d..45e6a915eb9 100644
--- a/libstdc++-v3/include/std/unordered_set
+++ b/libstdc++-v3/include/std/unordered_set
@@ -54,6 +54,7 @@
#define __glibcxx_want_generic_unordered_lookup
#define __glibcxx_want_node_extract
#define __glibcxx_want_nonmember_container_access
+#define __glibcxx_want_associative_heterogeneous_erasure
#include <bits/version.h>
#if __cplusplus >= 201703L
diff --git a/libstdc++-v3/testsuite/23_containers/map/modifiers/hetero/erase.cc
b/libstdc++-v3/testsuite/23_containers/map/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..20e0cbad261
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/map/modifiers/hetero/erase.cc
@@ -0,0 +1,95 @@
+// { dg-do run { target c++23 } }
+
+#include <map>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <compare>
+#include <cstring>
+#include <testsuite_hooks.h>
+
+struct X {
+ std::string s;
+ friend auto operator<=>(X const& a, X const& b) = default;
+};
+
+struct Y {
+ std::string_view s;
+ Y(std::string_view sv) : s(sv) {}
+ Y(std::string const& sv) : s(sv) {}
+ friend auto operator<=>(Y const& a, Y const& b) = default;
+ friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
+};
+
+using cmp = std::less<void>;
+
+// uniform erase
+void test1()
+{
+ std::map<X, int, cmp> amap{cmp{}};
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto n = amap.erase(X{std::string{"def"}});
+ VERIFY(n == 1);
+ VERIFY(amap.size() == 2);
+}
+
+// heterogeneous erase
+void test2()
+{
+ std::map<X, int, cmp> amap{cmp{}};
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto n = amap.erase(Y{std::string_view{"def"}});
+ VERIFY(n == 1);
+ VERIFY(amap.size() == 2);
+}
+
+// uniform extract
+void test3()
+{
+ std::map<X, int, cmp> amap{cmp{}};
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto node = amap.extract(X{std::string{"def"}});
+ VERIFY(node.key().s == X{"def"}.s);
+ VERIFY(node.mapped() == 2);
+ VERIFY(amap.size() == 2);
+}
+
+// heterogeneous extract
+void test4()
+{
+ std::map<X, int, cmp> amap{cmp{}};
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto node = amap.extract(Y{std::string_view{"def"}});
+ VERIFY(node.key().s == X{"def"}.s);
+ VERIFY(node.mapped() == 2);
+ VERIFY(amap.size() == 2);
+}
+
+struct Z {
+ std::string_view s;
+ friend auto operator<=>(X const& a, Z const& b) {
+ int cmp = std::memcmp(a.s.data(), b.s.data(), 3);
+ return cmp < 0 ? std::strong_ordering::less :
+ cmp == 0 ? std::strong_ordering::equal :
+ std::strong_ordering::greater;
+ }
+};
+
+void test5()
+{
+ std::map<X, int, cmp> amap{cmp{}};
+ amap.insert(
+ {{X{"abcdef"}, 1}, {X{"defghi"}, 2}, {X{"defjkl"}, 3}, {X{"jklmno"}, 4}});
+ auto n = amap.erase(Z{std::string_view{"def"}});
+ VERIFY(n == 2);
+ VERIFY(amap.size() == 2);
+}
+
+int main()
+{
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+}
diff --git
a/libstdc++-v3/testsuite/23_containers/multimap/modifiers/hetero/erase.cc
b/libstdc++-v3/testsuite/23_containers/multimap/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..bd4a1520c2b
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/multimap/modifiers/hetero/erase.cc
@@ -0,0 +1,95 @@
+// { dg-do run { target c++23 } }
+
+#include <map>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <compare>
+#include <cstring>
+#include <testsuite_hooks.h>
+
+struct X {
+ std::string s;
+ friend auto operator<=>(X const& a, X const& b) = default;
+};
+
+struct Y {
+ std::string_view s;
+ Y(std::string_view sv) : s(sv) {}
+ Y(std::string const& sv) : s(sv) {}
+ friend auto operator<=>(Y const& a, Y const& b) = default;
+ friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
+};
+
+using cmp = std::less<void>;
+
+// uniform erase
+void test1()
+{
+ std::multimap<X, int, cmp> amap{cmp{}};
+ amap.insert({{X{"abc"}, 0}, {X{"def"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto n = amap.erase(X{std::string{"def"}});
+ VERIFY(n == 2);
+ VERIFY(amap.size() == 2);
+}
+
+// heterogeneous erase
+void test2()
+{
+ std::multimap<X, int, cmp> amap{cmp{}};
+ amap.insert({{X{"abc"}, 0}, {X{"def"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto n = amap.erase(Y{std::string_view{"def"}});
+ VERIFY(n == 2);
+ VERIFY(amap.size() == 2);
+}
+
+// uniform extract
+void test3()
+{
+ std::multimap<X, int, cmp> amap{cmp{}};
+ amap.insert({{X{"abc"}, 0}, {X{"def"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ const auto node = amap.extract(X{std::string{"def"}});
+ VERIFY(node.key().s == "def");
+ VERIFY(node.mapped() == 1);
+ VERIFY(amap.size() == 3);
+}
+
+// heterogeneous extract
+void test4()
+{
+ std::multimap<X, int, cmp> amap{cmp{}};
+ amap.insert({{X{"abc"}, 0}, {X{"def"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ const auto node = amap.extract(Y{std::string_view{"def"}});
+ VERIFY(node.key().s == "def");
+ VERIFY(node.mapped() == 1);
+ VERIFY(amap.size() == 3);
+}
+
+struct Z {
+ std::string_view s;
+ friend auto operator<=>(X const& a, Z const& b) {
+ int cmp = std::memcmp(a.s.data(), b.s.data(), 3);
+ return cmp < 0 ? std::strong_ordering::less :
+ cmp == 0 ? std::strong_ordering::equal :
+ std::strong_ordering::greater;
+ }
+};
+
+void test5()
+{
+ std::map<X, int, cmp> amap{cmp{}};
+ amap.insert(
+ {{X{"abcdef"}, 1}, {X{"defghi"}, 2}, {X{"defjkl"}, 3}, {X{"jklmno"}, 4}});
+ auto n = amap.erase(Z{std::string_view{"def"}});
+ VERIFY(n == 2);
+ VERIFY(amap.size() == 2);
+}
+
+int main()
+{
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+}
diff --git
a/libstdc++-v3/testsuite/23_containers/multiset/modifiers/hetero/erase.cc
b/libstdc++-v3/testsuite/23_containers/multiset/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..8ebbc2604ba
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/multiset/modifiers/hetero/erase.cc
@@ -0,0 +1,88 @@
+// { dg-do run { target c++23 } }
+
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <compare>
+#include <cstring>
+#include <testsuite_hooks.h>
+
+struct X {
+ std::string s;
+ friend auto operator<=>(X const& a, X const& b) = default;
+};
+
+struct Y {
+ std::string_view s;
+ Y(std::string_view sv) : s(sv) {}
+ Y(std::string const& sv) : s(sv) {}
+ friend auto operator<=>(Y const& a, Y const& b) = default;
+ friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
+};
+
+using cmp = std::less<void>;
+
+void test1() // uniform erase
+{
+ std::multiset<X, cmp> aset{cmp{}};
+ aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+ auto n = aset.erase(X{std::string{"def"}});
+ VERIFY(n == 2);
+ VERIFY(aset.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+ std::multiset<X, cmp> aset{cmp{}};
+ aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+ auto n = aset.erase(Y{std::string_view{"def"}});
+ VERIFY(n == 2);
+ VERIFY(aset.size() == 2);
+}
+
+void test3() // uniform extract
+{
+ std::multiset<X, cmp> aset{cmp{}};
+ aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+ const auto node = aset.extract(X{std::string{"def"}});
+ VERIFY(node.value().s == "def");
+ VERIFY(aset.size() == 3);
+}
+
+void test4() // heterogeneous extract
+{
+ std::multiset<X, cmp> aset{cmp{}};
+ aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+ const auto node = aset.extract(Y{std::string_view{"def"}});
+ VERIFY(node.value().s == "def");
+ VERIFY(aset.size() == 3);
+}
+
+struct Z {
+ std::string_view s;
+ friend auto operator<=>(X const& a, Z const& b) {
+ int cmp = std::memcmp(a.s.data(), b.s.data(), 3);
+ return cmp < 0 ? std::strong_ordering::less :
+ cmp == 0 ? std::strong_ordering::equal :
+ std::strong_ordering::greater;
+ }
+};
+
+void test5()
+{
+ std::multiset<X, cmp> aset{cmp{}};
+ aset.insert({X{"abcdef"}, X{"defghi"}, X{"defjkl"}, X{"jklmno"}});
+ auto n = aset.erase(Z{std::string_view{"def"}});
+ VERIFY(n == 2);
+ VERIFY(aset.size() == 2);
+}
+
+int main()
+{
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+}
diff --git a/libstdc++-v3/testsuite/23_containers/set/modifiers/hetero/erase.cc
b/libstdc++-v3/testsuite/23_containers/set/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..8377a3f5728
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/set/modifiers/hetero/erase.cc
@@ -0,0 +1,88 @@
+// { dg-do run { target c++23 } }
+
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <compare>
+#include <cstring>
+#include <testsuite_hooks.h>
+
+struct X {
+ std::string s;
+ friend auto operator<=>(X const& a, X const& b) = default;
+};
+
+struct Y {
+ std::string_view s;
+ Y(std::string_view sv) : s(sv) {}
+ Y(std::string const& sv) : s(sv) {}
+ friend auto operator<=>(Y const& a, Y const& b) = default;
+ friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
+};
+
+using cmp = std::less<void>;
+
+void test1() // uniform erase
+{
+ std::set<X, cmp> aset{cmp{}};
+ aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+ auto n = aset.erase(X{std::string{"def"}});
+ VERIFY(n == 1);
+ VERIFY(aset.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+ std::set<X, cmp> aset{cmp{}};
+ aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+ auto n = aset.erase(Y{std::string_view{"def"}});
+ VERIFY(n == 1);
+ VERIFY(aset.size() == 2);
+}
+
+void test3() // uniform extract
+{
+ std::set<X, cmp> aset{cmp{}};
+ aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+ auto node = aset.extract(X{std::string{"def"}});
+ VERIFY(node.value().s == X{"def"}.s);
+ VERIFY(aset.size() == 2);
+}
+
+void test4() // heterogeneous extract
+{
+ std::set<X, cmp> aset{cmp{}};
+ aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+ auto node = aset.extract(Y{std::string_view{"def"}});
+ VERIFY(node.value().s == X{"def"}.s);
+ VERIFY(aset.size() == 2);
+}
+
+struct Z {
+ std::string_view s;
+ friend auto operator<=>(X const& a, Z const& b) {
+ int cmp = std::memcmp(a.s.data(), b.s.data(), 3);
+ return cmp < 0 ? std::strong_ordering::less :
+ cmp == 0 ? std::strong_ordering::equal :
+ std::strong_ordering::greater;
+ }
+};
+
+void test5()
+{
+ std::set<X, cmp> aset{cmp{}};
+ aset.insert({X{"abcdef"}, X{"defghi"}, X{"defjkl"}, X{"jklmno"}});
+ auto n = aset.erase(Z{std::string_view{"def"}});
+ VERIFY(n == 2);
+ VERIFY(aset.size() == 2);
+}
+
+int main()
+{
+ test1();
+ test2();
+ test3();
+ test4();
+ test5();
+}
diff --git
a/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc
b/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..976b574fde2
--- /dev/null
+++
b/libstdc++-v3/testsuite/23_containers/unordered_map/modifiers/hetero/erase.cc
@@ -0,0 +1,76 @@
+// { dg-do run { target c++23 } }
+
+#include <unordered_map>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <functional>
+#include <compare>
+#include <testsuite_hooks.h>
+
+struct X {
+ std::string s;
+ friend bool operator==(X const& a, X const& b) = default;
+};
+
+struct Y {
+ std::string_view s;
+ Y(std::string_view sv) : s(sv) {}
+ Y(std::string const& sv) : s(sv) {}
+ friend bool operator==(Y const& a, Y const& b) = default;
+ friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
+};
+
+struct Hash {
+ using is_transparent = void;
+ template <typename T>
+ auto operator()(T const& t) const { return
std::hash<decltype(T::s)>{}(t.s); }
+};
+
+using Equal = std::equal_to<void>;
+
+void test1() // uniform erase
+{
+ std::unordered_map<X, int, Hash, Equal> amap;
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto n = amap. erase(X{ std::string{"def"}});
+ VERIFY(n == 1);
+ VERIFY(amap.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+ std::unordered_map<X, int, Hash, Equal> amap;
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto n = amap. erase(Y{ std::string_view{"def"}});
+ VERIFY(n == 1);
+ VERIFY(amap.size() == 2);
+}
+
+void test3() // uniform extract
+{
+ std::unordered_map<X, int, Hash, Equal> amap;
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto node = amap. extract(X{ std::string{"def"}});
+ VERIFY(node.key().s == X{"def"}.s);
+ VERIFY(node.mapped() == 2);
+ VERIFY(amap.size() == 2);
+}
+
+void test4() // heterogeneous extract
+{
+ std::unordered_map<X, int, Hash, Equal> amap;
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
+ auto node = amap. extract(Y{ std::string_view{"def"}});
+ VERIFY(node.key().s == X{"def"}.s);
+ VERIFY(node.mapped() == 2);
+ VERIFY(amap.size() == 2);
+}
+
+int main()
+{
+ test1();
+ test2();
+ test3();
+ test4();
+}
diff --git
a/libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc
b/libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..df8f2161aa6
--- /dev/null
+++
b/libstdc++-v3/testsuite/23_containers/unordered_multimap/modifiers/hetero/erase.cc
@@ -0,0 +1,75 @@
+// { dg-do run { target c++23 } }
+
+#include <unordered_map>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <functional>
+#include <testsuite_hooks.h>
+
+struct X {
+ std::string s;
+ friend bool operator==(X const& a, X const& b) = default;
+};
+
+struct Y {
+ std::string_view s;
+ Y(std::string_view sv) : s(sv) {}
+ Y(std::string const& sv) : s(sv) {}
+ friend bool operator==(Y const& a, Y const& b) = default;
+ friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
+};
+
+struct Hash {
+ using is_transparent = void;
+ template <typename T>
+ auto operator()(T const& t) const { return
std::hash<decltype(T::s)>{}(t.s); }
+};
+
+using Equal = std::equal_to<void>;
+
+void test1() // uniform erase
+{
+ std::unordered_multimap<X, int, Hash, Equal> amap;
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"def"}, 3}, {X{"ghi"}, 4}});
+ auto n = amap. erase(X{ std::string{"def"}});
+ VERIFY(n == 2);
+ VERIFY(amap.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+ std::unordered_multimap<X, int, Hash, Equal> amap;
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"def"}, 3}, {X{"ghi"}, 4}});
+ auto n = amap. erase(Y{ std::string_view{"def"}});
+ VERIFY(n == 2);
+ VERIFY(amap.size() == 2);
+}
+
+void test3() // uniform extract
+{
+ std::unordered_multimap<X, int, Hash, Equal> amap;
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"def"}, 3}, {X{"ghi"}, 4}});
+ auto node = amap. extract(X{ std::string{"def"}});
+ VERIFY(node.key().s == X{"def"}.s);
+ VERIFY(node.mapped() == 2 || node.mapped() == 3);
+ VERIFY(amap.size() == 3);
+}
+
+void test4() // heterogeneous extract
+{
+ std::unordered_multimap<X, int, Hash, Equal> amap;
+ amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"def"}, 3}, {X{"ghi"}, 4}});
+ auto node = amap. extract(Y{ std::string_view{"def"}});
+ VERIFY(node.key().s == X{"def"}.s);
+ VERIFY(node.mapped() == 2 || node.mapped() == 3);
+ VERIFY(amap.size() == 3);
+}
+
+int main()
+{
+ test1();
+ test2();
+ test3();
+ test4();
+}
diff --git
a/libstdc++-v3/testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc
b/libstdc++-v3/testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..e886abe2b54
--- /dev/null
+++
b/libstdc++-v3/testsuite/23_containers/unordered_multiset/modifiers/hetero/erase.cc
@@ -0,0 +1,73 @@
+// { dg-do run { target c++23 } }
+
+#include <unordered_set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <functional>
+#include <testsuite_hooks.h>
+
+struct X {
+ std::string s;
+ friend bool operator==(X const& a, X const& b) = default;
+};
+
+struct Y {
+ std::string_view s;
+ Y(std::string_view sv) : s(sv) {}
+ Y(std::string const& sv) : s(sv) {}
+ friend bool operator==(Y const& a, Y const& b) = default;
+ friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
+};
+
+struct Hash {
+ using is_transparent = void;
+ template <typename T>
+ auto operator()(T const& t) const { return
std::hash<decltype(T::s)>{}(t.s); }
+};
+
+using Equal = std::equal_to<void>;
+
+void test1() // uniform erase
+{
+ std::unordered_multiset<X, Hash, Equal> aset;
+ aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+ auto n = aset. erase(X{ std::string{"def"}});
+ VERIFY(n == 2);
+ VERIFY(aset.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+ std::unordered_multiset<X, Hash, Equal> aset;
+ aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+ auto n = aset. erase(Y{ std::string_view{"def"}});
+ VERIFY(n == 2);
+ VERIFY(aset.size() == 2);
+}
+
+void test3() // uniform extract
+{
+ std::unordered_multiset<X, Hash, Equal> aset;
+ aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+ auto node = aset. extract(X{ std::string{"def"}});
+ VERIFY(node.value().s == X{"def"}.s);
+ VERIFY(aset.size() == 3);
+}
+
+void test4() // heterogeneous extract
+{
+ std::unordered_multiset<X, Hash, Equal> aset;
+ aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
+ auto node = aset. extract(Y{ std::string_view{"def"}});
+ VERIFY(node.value().s == X{"def"}.s);
+ VERIFY(aset.size() == 3);
+}
+
+int main()
+{
+ test1();
+ test2();
+ test3();
+ test4();
+}
diff --git
a/libstdc++-v3/testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc
b/libstdc++-v3/testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc
new file mode 100644
index 00000000000..6b60fa40198
--- /dev/null
+++
b/libstdc++-v3/testsuite/23_containers/unordered_set/modifiers/hetero/erase.cc
@@ -0,0 +1,73 @@
+// { dg-do run { target c++23 } }
+
+#include <unordered_set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <functional>
+#include <testsuite_hooks.h>
+
+struct X {
+ std::string s;
+ friend bool operator==(X const& a, X const& b) = default;
+};
+
+struct Y {
+ std::string_view s;
+ Y(std::string_view sv) : s(sv) {}
+ Y(std::string const& sv) : s(sv) {}
+ friend bool operator==(Y const& a, Y const& b) = default;
+ friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
+};
+
+struct Hash {
+ using is_transparent = void;
+ template <typename T>
+ auto operator()(T const& t) const { return
std::hash<decltype(T::s)>{}(t.s); }
+};
+
+using Equal = std::equal_to<void>;
+
+void test1() // uniform erase
+{
+ std::unordered_set<X, Hash, Equal> aset;
+ aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+ auto n = aset.erase(X{std::string{"def"}});
+ VERIFY(n == 1);
+ VERIFY(aset.size() == 2);
+}
+
+void test2() // heterogeneous erase
+{
+ std::unordered_set<X, Hash, Equal> aset;
+ aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+ auto n = aset.erase(Y{std::string_view{"def"}});
+ VERIFY(n == 1);
+ VERIFY(aset.size() == 2);
+}
+
+void test3() // uniform extract
+{
+ std::unordered_set<X, Hash, Equal> aset;
+ aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+ auto node = aset.extract(X{std::string{"def"}});
+ VERIFY(node.value().s == X{"def"}.s);
+ VERIFY(aset.size() == 2);
+}
+
+void test4() // heterogeneous extract
+{
+ std::unordered_set<X, Hash, Equal> aset;
+ aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
+ auto node = aset.extract(Y{std::string_view{"def"}});
+ VERIFY(node.value().s == X{"def"}.s);
+ VERIFY(aset.size() == 2);
+}
+
+int main()
+{
+ test1();
+ test2();
+ test3();
+ test4();
+}
--
2.52.0