libstdc++: container heterogeneous insertion (P2363) [PR117402]

Implements P2353R5 "Extending associative containers with the
remaining heterogeneous overloads". Adds overloads templated on
heterogeneous key types for several members of associative
containers, particularly insertions:

                      /-- unordered --\
 set  map  mset mmap set  map  mset mmap
  @    .    .    .    @    .    .    .    insert
  .    @    .    .    .    @    .    .    op[], at, try_emplace,
                                            insert_or_assign
  .    .    .    .    @    @    @    @    bucket

(Nothing is added to the multiset or multimap tree containers.)
All the insert*() and try_emplace() members also get a hinted
overload.  The at() members get const and non-const overloads.

The new overloads enforce concept __heterogeneous_tree_key or
__heterogeneous_hash_key, as in P2077, to enforce that the
function objects provided meet requirements, and that the key
supplied is not an iterator or the native key. Insertions
implicitly construct the required key_type object from the
argument, by move where permitted.

libstdc++-v3/ChangeLog:
	PR libstdc++/117402
	* include/bits/stl_map.h (operator[], at (2x), try_emplace (2x),
	insert_or_assign (2x)): Add overloads.
	* include/bits/unordered_map.h (operator[], at (2x),
	try_emplace (2x), insert_or_assign (2x), bucket (2x)): Add overloads.
	* include/bits/stl_set.h (insert (2x)): Add overloads.
	* include/bits/unordered_set.h (insert (2x), bucket (2x)): Add overloads.
	* include/bits/hashtable.h (_M_bucket_tr, _M_insert_tr): Define.
	* include/bits/hashtable_policy.h (_M_at_tr (2x)): Define.
	* include/bits/stl_tree.h (_M_emplace_here, _M_get_insert_unique_pos_tr,
	_M_get_insert_hint_unique_pos_tr): Define new heterogeneous insertion
	code path for set and map.
	* include/bits/version.def (associative_heterogeneous_insertion):
	Define.
	* include/bits/version.h: Regenerate.
	* include/std/map (__glibcxx_want_associative_heterogeneous_insertion):
	Define macro.
	* include/std/set: Same.
	* include/std/unordered_map: Same.
	* include/std/unordered_set: Same.
	* testsuite/23_containers/map/modifiers/hetero/insert.cc: New tests.
	* testsuite/23_containers/set/modifiers/hetero/insert.cc: Same.
	* testsuite/23_containers/unordered_map/modifiers/hetero/insert.cc:
	Same.
	* testsuite/23_containers/unordered_multimap/modifiers/hetero/insert.cc:
	Same.
	* testsuite/23_containers/unordered_multiset/modifiers/hetero/insert.cc:
	Same.
	* testsuite/23_containers/unordered_set/modifiers/hetero/insert.cc:
	Same.
This commit is contained in:
Nathan Myers
2026-01-12 22:49:59 -05:00
parent 37980a5a78
commit 94d5ca4583
19 changed files with 2445 additions and 27 deletions

View File

@@ -700,6 +700,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
bucket(const key_type& __k) const
{ return _M_bucket_index(this->_M_hash_code(__k)); }
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <typename _Kt>
size_type
_M_bucket_tr(const _Kt& __k) const
{ return _M_bucket_index(this->_M_hash_code_tr(__k)); }
#endif
local_iterator
begin(size_type __bkt)
{
@@ -1097,6 +1104,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
std::pair<iterator, bool>
try_emplace(const_iterator, _KType&& __k, _Args&&... __args)
{
// Note we ignore the hint argument.
__hash_code __code;
size_type __bkt;
if (auto __loc = _M_locate(__k))
@@ -1117,6 +1125,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__node._M_node = nullptr;
return { __it, true };
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template<typename _Kt>
std::pair<iterator, bool>
_M_insert_tr(_Kt&& __k)
{
auto __loc = _M_locate_tr(__k);
if (__loc)
return { iterator(__loc), false };
_Scoped_node __node(
this->_M_allocate_node(std::forward<_Kt>(__k)), this);
auto __it = _M_insert_unique_node(
__loc._M_bucket_index, __loc._M_hash_code, __node._M_node);
__node._M_node = nullptr;
return { __it, true };
}
#endif
#endif
void
@@ -2363,6 +2389,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__node._M_node = nullptr;
return { __pos, true };
}
#pragma GCC diagnostic pop
template<typename _Key, typename _Value, typename _Alloc,

View File

@@ -872,6 +872,26 @@ namespace __detail
__throw_out_of_range(__N("unordered_map::at"));
return __ite->second;
}
template <typename _Kt>
mapped_type&
_M_at_tr(const _Kt& __k)
{
auto __ite = static_cast<__hashtable*>(this)->_M_find_tr(__k);
if (!__ite._M_cur)
__throw_out_of_range(__N("unordered_map::at"));
return __ite->second;
}
template <typename _Kt>
const mapped_type&
_M_at_tr(const _Kt& __k) const
{
auto __ite = static_cast<const __hashtable*>(this)->_M_find_tr(__k);
if (!__ite._M_cur)
__throw_out_of_range(__N("unordered_map::at"));
return __ite->second;
}
};
template<typename _Key, typename _Val, typename _Alloc, typename _Equal,
@@ -1413,8 +1433,7 @@ namespace __detail
template<typename _Kt>
bool
_M_key_equals_tr(const _Kt& __k,
const _Hash_node_value<_Value,
__hash_cached::value>& __n) const
const _Hash_node_value<_Value, __hash_cached::value>& __n) const
{
static_assert(
__is_invocable<const _Equal&, const _Kt&, const _Key&>{},
@@ -1439,8 +1458,7 @@ namespace __detail
template<typename _Kt>
bool
_M_equals_tr(const _Kt& __k, __hash_code __c,
const _Hash_node_value<_Value,
__hash_cached::value>& __n) const
const _Hash_node_value<_Value, __hash_cached::value>& __n) const
{
if constexpr (__hash_cached::value)
if (__c != __n._M_hash_code)

View File

@@ -522,6 +522,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
* subscript. If the key does not exist, a pair with that key
* is created using default values, which is then returned.
*
* If a heterogeneous key matches a range of elements, the first is
* chosen.
*
* Lookup requires logarithmic time.
*/
mapped_type&
@@ -560,6 +563,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
}
#endif
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_tree_key<map> _Kt>
mapped_type&
operator[](_Kt&& __k)
{ return try_emplace(std::forward<_Kt>(__k)).first->second; }
#endif
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 464. Suggestion for new member functions in standard containers.
/**
@@ -568,6 +578,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
* @return A reference to the data whose key is equivalent to @a __k, if
* such a data is present in the %map.
* @throw std::out_of_range If no such data is present.
*
* If a heterogeneous key __k matches a range of elements, the
* first is chosen.
*/
mapped_type&
at(const key_type& __k)
@@ -578,6 +591,18 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
return (*__i).second;
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_tree_key<map> _Kt>
mapped_type&
at(const _Kt& __k)
{
iterator __i = lower_bound(__k);
if (__i == end() || key_comp()(__k, (*__i).first))
__throw_out_of_range(__N("map::at"));
return (*__i).second;
}
#endif
const mapped_type&
at(const key_type& __k) const
{
@@ -587,6 +612,18 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
return (*__i).second;
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_tree_key<map> _Kt>
const mapped_type&
at(const _Kt& __k) const
{
const_iterator __i = lower_bound(__k);
if (__i == end() || key_comp()(__k, (*__i).first))
__throw_out_of_range(__N("map::at"));
return (*__i).second;
}
#endif
// modifiers
#if __cplusplus >= 201103L
/**
@@ -746,6 +783,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
* first element (the key) is not already present in the %map.
* If a %pair is not inserted, this function has no effect.
*
* If a heterogeneous key __k matches a range of elements, an iterator
* to the first is returned.
*
* Insertion requires logarithmic time.
*/
template <typename... _Args>
@@ -781,6 +821,26 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
return {__i, false};
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_tree_key<map> _Kt, typename ..._Args>
pair<iterator, bool>
try_emplace(_Kt&& __k, _Args&&... __args)
{
iterator __i;
auto [__left, __node] = _M_t._M_get_insert_unique_pos_tr(__k);
if (__node)
{
__i = _M_t._M_emplace_here(__left == __node, __node,
std::piecewise_construct,
std::forward_as_tuple(std::forward<_Kt>(__k)),
std::forward_as_tuple(std::forward<_Args>(__args)...));
return { __i, true };
}
__i = iterator(__left);
return { __i, false };
}
#endif
/**
* @brief Attempts to build and insert a std::pair into the %map.
*
@@ -806,6 +866,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
* https://gcc.gnu.org/onlinedocs/libstdc++/manual/associative.html#containers.associative.insert_hints
* for more on @a hinting.
*
* If a heterogeneous key __k matches a range of elements, an iterator
* to the first is returned.
*
* Insertion requires logarithmic time (if the hint is not taken).
*/
template <typename... _Args>
@@ -845,6 +908,26 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
}
#endif
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_tree_key<map> _Kt, typename ..._Args>
iterator
try_emplace(const_iterator __hint, _Kt&& __k, _Args&&... __args)
{
iterator __i;
auto [__left, __node] =
_M_t._M_get_insert_hint_unique_pos_tr(__hint, __k);
if (__node)
{
__i = _M_t._M_emplace_here(__left == __node, __node,
std::piecewise_construct,
std::forward_as_tuple(std::forward<_Kt>(__k)),
std::forward_as_tuple(std::forward<_Args>(__args)...));
}
else __i = iterator(__left);
return __i;
}
#endif
/**
* @brief Attempts to insert a std::pair into the %map.
* @param __x Pair to be inserted (see std::make_pair for easy
@@ -896,7 +979,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
return _M_t._M_emplace_unique(std::forward<_Pair>(__x));
}
#endif
/// @}
///@}
#if __cplusplus >= 201103L
/**
@@ -1046,6 +1129,31 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
return {__i, false};
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_tree_key<map> _Kt, typename _Obj>
pair<iterator, bool>
insert_or_assign(_Kt&& __k, _Obj&& __obj)
{
iterator __i;
auto [__left, __node] =_M_t._M_get_insert_unique_pos_tr(__k);
if (__node)
{
__i = _M_t._M_emplace_here(__left == __node, __node,
std::piecewise_construct,
std::forward_as_tuple(std::forward<_Kt>(__k)),
std::forward_as_tuple(std::forward<_Obj>(__obj)));
return { __i, true };
}
__i = iterator(__left);
(*__i).second = std::forward<_Obj>(__obj);
return { __i, false };
}
#endif
///@}
#endif
#if __cplusplus > 201402L
///@{
/**
* @brief Attempts to insert or assign a std::pair into the %map.
* @param __hint An iterator that serves as a hint as to where the
@@ -1064,6 +1172,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
* If the %pair was already in the %map, the .second of the %pair
* is assigned from __obj.
*
* If a heterogeneous key __k matches a range of elements, the first
* is chosen.
*
* Insertion requires logarithmic time.
*/
template <typename _Obj>
@@ -1105,6 +1216,28 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
(*__i).second = std::forward<_Obj>(__obj);
return __i;
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_tree_key<map> _Kt, typename _Obj>
iterator
insert_or_assign(const_iterator __hint, _Kt&& __k, _Obj&& __obj)
{
iterator __i;
auto [__left, __node] =
_M_t._M_get_insert_hint_unique_pos_tr(__hint, __k);
if (__node)
{
return _M_t._M_emplace_here(__left == __node, __node,
std::piecewise_construct,
std::forward_as_tuple(std::forward<_Kt>(__k)),
std::forward_as_tuple(std::forward<_Obj>(__obj)));
}
__i = iterator(__left);
(*__i).second = std::forward<_Obj>(__obj);
return __i;
}
#endif
///@}
#endif
#if __cplusplus >= 201103L

View File

@@ -548,6 +548,22 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
}
#endif
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_tree_key<set> _Kt>
pair<iterator, bool>
insert(_Kt&& __k)
{
auto [__left, __node] =_M_t._M_get_insert_unique_pos_tr(__k);
if (__node)
{
iterator __i = _M_t._M_emplace_here(
(__left == __node), __node, std::forward<_Kt>(__k));
return { __i, true };
}
return { iterator(__left), false };
}
#endif
/**
* @brief Attempts to insert an element into the %set.
* @param __position An iterator that serves as a hint as to where the
@@ -565,6 +581,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
* For more on @a hinting, see:
* https://gcc.gnu.org/onlinedocs/libstdc++/manual/associative.html#containers.associative.insert_hints
*
* If a heterogeneous key __k matches a range of elements, an iterator
* to the first is returned.
*
* Insertion requires logarithmic time (if the hint is not taken).
*/
iterator
@@ -577,6 +596,22 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
{ return _M_t._M_insert_unique_(__position, std::move(__x)); }
#endif
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_tree_key<set> _Kt>
iterator
insert(const_iterator __position, _Kt&& __k)
{
auto [__left, __node] =
_M_t._M_get_insert_hint_unique_pos_tr(__position, __k);
if (__node)
return _M_t._M_emplace_here(
(__left == __node), __node, std::forward<_Kt>(__k));
else
return iterator(__left);
}
#endif
///@}
/**
* @brief A template function that attempts to insert a range
* of elements.

View File

@@ -1470,6 +1470,20 @@ namespace __rb_tree
_M_get_insert_hint_equal_pos(const_iterator __pos,
const key_type& __k);
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <typename... _Args>
iterator
_M_emplace_here(bool __place_left, _Base_ptr __node, _Args&&... __args);
template <typename _Kt>
pair<_Base_ptr, _Base_ptr>
_M_get_insert_unique_pos_tr(const _Kt& __k);
template <typename _Kt>
pair<_Base_ptr, _Base_ptr>
_M_get_insert_hint_unique_pos_tr(const_iterator, const _Kt& __k);
#endif
private:
#if __cplusplus >= 201103L
template<typename _Arg, typename _NodeGen>
@@ -2060,7 +2074,7 @@ namespace __rb_tree
_M_move_assign(_Rb_tree&, false_type);
#endif
#if __glibcxx_node_extract // >= C++17
#ifdef __glibcxx_node_extract // >= C++17
static _Node_ptr
_S_adapt(typename _Node_alloc_traits::pointer __ptr)
{
@@ -2446,7 +2460,7 @@ namespace __rb_tree
for (; __first != __last; ++__first)
_M_insert_equal_(end(), *__first, __roan);
}
#endif
#endif // C++11
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
@@ -2830,6 +2844,56 @@ namespace __rb_tree
return _Res(__x, __y);
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
// Multiple elements may compare equal to __k. Identify the first
// of any such elements, or insert normally.
template <typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
template <typename _Kt>
auto
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_get_insert_unique_pos_tr(const _Kt& __k)
-> pair<_Base_ptr, _Base_ptr>
{
if (size() == 0)
return { _M_end(), _M_end() }; // Insert as root.
_Base_ptr __x = _M_begin(), __y = __x;
bool __k_le_y = false;
do
{
__y = __x;
__k_le_y = ! _M_key_compare(_S_key(__x), __k);
__x = __k_le_y ? _S_left(__x) : _S_right(__x);
}
while (__x);
// If !__k_le_y, __k > *__y;
// If __y is rightmost, put at _M_right under *__y.
// else if __k < *(__y+1), put at _M_right under *__y.
// else __k == *(__y+1), do not insert, report (__y+1).
// else, __k_le_y, __k <= *__y;
// If __k < *__Y, put at _M_left under *__y.
// else __k == *__y, do not insert, report __y.
auto __j = iterator(__y);
if (! __k_le_y) // k > *__y
{
if (__y == _M_rightmost())
return { {}, __y }; // Place to right under __y.
++__j;
}
if (_M_key_compare(__k, _S_key(__j._M_node)))
{
if (__k_le_y)
return { __y, __y }; // Place to left under __y.
else
return { {}, __y }; // Place to right under __y.
}
return { __j._M_node, {} }; // No insert.
}
#endif
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
#if __cplusplus >= 201103L
@@ -2936,6 +3000,59 @@ namespace __rb_tree
return _Res(__position._M_node, _Base_ptr());
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
template <typename _Kt>
auto
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_get_insert_hint_unique_pos_tr(const_iterator __hint, const _Kt& __k)
-> pair<_Base_ptr, _Base_ptr>
{
auto __node =__hint._M_node;
if (__node == _M_end())
{
if (size() > 0 && _M_key_compare(_S_key(_M_rightmost()), __k))
return { {}, _M_rightmost() };
return _M_get_insert_unique_pos_tr(__k);
}
if (_M_key_compare(__k, _S_key(__node)))
{ // First, try before...
if (__node == _M_leftmost()) // begin()
return { _M_leftmost(), _M_leftmost() };
iterator __before(__node);
--__before;
if (_M_key_compare(_S_key(__before._M_node), __k))
{
if (!_S_right(__before._M_node))
return { {}, __before._M_node }; // put right
return { __node, __node }; // put left;
}
return _M_get_insert_unique_pos_tr(__k);
}
if (_M_key_compare(_S_key(__node), __k))
{ // ... then try after.
if (__node == _M_rightmost())
return { {}, _M_rightmost() };
iterator __after(__node);
++__after;
if (_M_key_compare(__k, _S_key(__after._M_node)))
{
if (!_S_right(__node))
return { {}, __node };
return { __after._M_node, __after._M_node };
}
return _M_get_insert_unique_pos_tr(__k);
}
// Equal to __k; check if any more to the left.
iterator __before(__node);
if (__node == _M_leftmost() ||
_M_key_compare(_S_key((--__before)._M_node), __k))
{ return { __node, {} }; }
return _M_get_insert_unique_pos_tr(__k);
}
#endif
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
#if __cplusplus >= 201103L
@@ -3155,8 +3272,33 @@ namespace __rb_tree
return __z._M_insert(__res);
return __z._M_insert_equal_lower();
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
// If __pos.second == &_M_impl._M_header, insert at root;
// else if __pos.first == __pos.second, insert at __pos.second._M_left;
// else insert at __pos.second._M_right, and rebalance.
template <typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
template <typename... _Args>
auto
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_emplace_here(bool __place_left, _Base_ptr __node, _Args&&... __args)
-> iterator
{
_Auto_node __z(*this, std::forward<_Args>(__args)...);
_Base_ptr __base_z = __z._M_node->_M_base_ptr();
_Node_traits::_S_insert_and_rebalance(
__place_left, __base_z, __node, _M_impl._M_header);
__z._M_node = nullptr;
++_M_impl._M_node_count;
return iterator(__base_z);
}
#endif
#endif // >= C++11
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>

View File

@@ -456,6 +456,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
emplace(_Args&&... __args)
{ return _M_h.emplace(std::forward<_Args>(__args)...); }
///@{
/**
* @brief Attempts to build and insert a std::pair into the
* %unordered_map.
@@ -520,6 +521,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
#endif // node_extract
#ifdef __glibcxx_unordered_map_try_emplace // C++ >= 17 && HOSTED
///@{
/**
* @brief Attempts to build and insert a std::pair into the
* %unordered_map.
@@ -558,6 +560,18 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
std::forward<_Args>(__args)...);
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_map> _Kt, typename ..._Args>
pair<iterator, bool>
try_emplace(_Kt&& __k, _Args&&... __args)
{
return _M_h.try_emplace(cend(),
std::forward<_Kt>(__k), std::forward<_Args>(__args)...);
}
#endif
///@}
///@{
/**
* @brief Attempts to build and insert a std::pair into the
* %unordered_map.
@@ -605,6 +619,17 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
}
#endif // __glibcxx_unordered_map_try_emplace
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_map> _Kt, typename ..._Args>
iterator
try_emplace(const_iterator __hint, _Kt&& __k, _Args&&... __args)
{
return _M_h.try_emplace(__hint,
std::forward<_Kt>(__k), std::forward<_Args>(__args)...).first;
}
#endif
///@}
///@{
/**
* @brief Attempts to insert a std::pair into the %unordered_map.
@@ -722,6 +747,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
#endif
#ifdef __glibcxx_unordered_map_try_emplace // >= C++17 && HOSTED
///@{
/**
* @brief Attempts to insert a std::pair into the %unordered_map.
* @param __k Key to use for finding a possibly existing pair in
@@ -765,6 +791,21 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
return __ret;
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_map> _Kt, typename _Obj>
pair<iterator, bool>
insert_or_assign(_Kt&& __k, _Obj&& __obj)
{
auto __ret = _M_h.try_emplace(
cend(), std::forward<_Kt>(__k), std::forward<_Obj>(__obj));
if (!__ret.second)
__ret.first->second = std::forward<_Obj>(__obj);
return __ret;
}
#endif
///@}
///@{
/**
* @brief Attempts to insert a std::pair into the %unordered_map.
* @param __hint An iterator that serves as a hint as to where the
@@ -813,6 +854,20 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
__ret.first->second = std::forward<_Obj>(__obj);
return __ret.first;
}
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_map> _Kt, typename _Obj>
iterator
insert_or_assign(const_iterator __hint, _Kt&& __k, _Obj&& __obj)
{
auto __ret = _M_h.try_emplace(__hint,
std::forward<_Kt>(__k), std::forward<_Obj>(__obj));
if (!__ret.second)
__ret.first->second = std::forward<_Obj>(__obj);
return __ret.first;
}
#endif
///@}
#endif // unordered_map_try_emplace
///@{
@@ -839,6 +894,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
{ return _M_h.erase(__position); }
///@}
///@{
/**
* @brief Erases elements according to the provided key.
* @param __x Key of element to be erased.
@@ -861,6 +917,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
erase(_Kt&& __key)
{ return _M_h._M_erase_tr(__key); }
#endif
///@}
/**
* @brief Erases a [__first,__last) range of elements from an
@@ -1089,6 +1146,15 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
mapped_type&
operator[](key_type&& __k)
{ return _M_h[std::move(__k)]; }
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_map> _Kt>
mapped_type&
operator[](_Kt&& __k)
{
return try_emplace(std::forward<_Kt>(__k)).first->second;
}
#endif
///@}
///@{
@@ -1103,9 +1169,23 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
at(const key_type& __k)
{ return _M_h.at(__k); }
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_map> _Kt>
mapped_type&
at(const _Kt& __k)
{ return _M_h._M_at_tr(__k); }
#endif
const mapped_type&
at(const key_type& __k) const
{ return _M_h.at(__k); }
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_map> _Kt>
const mapped_type&
at(const _Kt& __k) const
{ return _M_h._M_at_tr(__k); }
#endif
///@}
// bucket interface.
@@ -1129,6 +1209,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
bucket_size(size_type __n) const
{ return _M_h.bucket_size(__n); }
///@{
/*
* @brief Returns the bucket index of a given element.
* @param __key A key instance.
@@ -1138,6 +1219,14 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
bucket(const key_type& __key) const
{ return _M_h.bucket(__key); }
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_map> _Kt>
size_type
bucket(const _Kt& __key) const
{ return _M_h._M_bucket_tr(__key); }
#endif
///@}
/**
* @brief Returns a read/write iterator pointing to the first bucket
* element.
@@ -1928,6 +2017,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
{ return _M_h.erase(__position); }
///@}
///@{
/**
* @brief Erases elements according to the provided key.
* @param __x Key of elements to be erased.
@@ -1946,9 +2036,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
#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); }
erase(_Kt&& __x)
{ return _M_h._M_erase_tr(__x); }
#endif
///@}
/**
* @brief Erases a [__first,__last) range of elements from an
@@ -2176,6 +2268,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
bucket_size(size_type __n) const
{ return _M_h.bucket_size(__n); }
///@{
/*
* @brief Returns the bucket index of a given element.
* @param __key A key instance.
@@ -2185,6 +2278,14 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
bucket(const key_type& __key) const
{ return _M_h.bucket(__key); }
#ifdef __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_multimap> _Kt>
size_type
bucket(const _Kt& __key) const
{ return _M_h._M_bucket_tr(__key); }
#endif
///@}
/**
* @brief Returns a read/write iterator pointing to the first bucket
* element.

View File

@@ -493,6 +493,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
std::pair<iterator, bool>
insert(value_type&& __x)
{ return _M_h.insert(std::move(__x)); }
#if __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_set> _Kt>
std::pair<iterator, bool>
insert(_Kt&& __x)
{ return _M_h._M_insert_tr(std::forward<_Kt>(__x)); }
#endif
///@}
///@{
@@ -522,6 +529,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
iterator
insert(const_iterator __hint, value_type&& __x)
{ return _M_h.insert(__hint, std::move(__x)); }
#if __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_set> _Kt>
iterator
insert(const_iterator, _Kt&& __x)
{ return _M_h._M_insert_tr(std::forward<_Kt>(__x)).first; }
#endif
///@}
/**
@@ -761,9 +775,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
#ifdef __glibcxx_generic_unordered_lookup // C++ >= 20 && HOSTED
template<typename _Kt>
auto
find(const _Kt& __k)
-> decltype(_M_h._M_find_tr(__k))
{ return _M_h._M_find_tr(__k); }
find(const _Kt& __x)
-> decltype(_M_h._M_find_tr(__x))
{ return _M_h._M_find_tr(__x); }
#endif
const_iterator
@@ -773,9 +787,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
#ifdef __glibcxx_generic_unordered_lookup // C++ >= 20 && HOSTED
template<typename _Kt>
auto
find(const _Kt& __k) const
-> decltype(_M_h._M_find_tr(__k))
{ return _M_h._M_find_tr(__k); }
find(const _Kt& __x) const
-> decltype(_M_h._M_find_tr(__x))
{ return _M_h._M_find_tr(__x); }
#endif
///@}
@@ -796,9 +810,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
#ifdef __glibcxx_generic_unordered_lookup // C++ >= 20 && HOSTED
template<typename _Kt>
auto
count(const _Kt& __k) const
-> decltype(_M_h._M_count_tr(__k))
{ return _M_h._M_count_tr(__k); }
count(const _Kt& __x) const
-> decltype(_M_h._M_count_tr(__x))
{ return _M_h._M_count_tr(__x); }
#endif
///@}
@@ -815,9 +829,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
template<typename _Kt>
auto
contains(const _Kt& __k) const
-> decltype(_M_h._M_find_tr(__k), void(), true)
{ return _M_h._M_find_tr(__k) != _M_h.end(); }
contains(const _Kt& __x) const
-> decltype(_M_h._M_find_tr(__x), void(), true)
{ return _M_h._M_find_tr(__x) != _M_h.end(); }
///@}
#endif
@@ -837,9 +851,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
#ifdef __glibcxx_generic_unordered_lookup // C++ >= 20 && HOSTED
template<typename _Kt>
auto
equal_range(const _Kt& __k)
-> decltype(_M_h._M_equal_range_tr(__k))
{ return _M_h._M_equal_range_tr(__k); }
equal_range(const _Kt& __x)
-> decltype(_M_h._M_equal_range_tr(__x))
{ return _M_h._M_equal_range_tr(__x); }
#endif
std::pair<const_iterator, const_iterator>
@@ -849,9 +863,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
#ifdef __glibcxx_generic_unordered_lookup // C++ >= 20 && HOSTED
template<typename _Kt>
auto
equal_range(const _Kt& __k) const
-> decltype(_M_h._M_equal_range_tr(__k))
{ return _M_h._M_equal_range_tr(__k); }
equal_range(const _Kt& __x) const
-> decltype(_M_h._M_equal_range_tr(__x))
{ return _M_h._M_equal_range_tr(__x); }
#endif
///@}
@@ -876,6 +890,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
bucket_size(size_type __n) const
{ return _M_h.bucket_size(__n); }
///@{
/*
* @brief Returns the bucket index of a given element.
* @param __key A key instance.
@@ -885,6 +900,14 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
bucket(const key_type& __key) const
{ return _M_h.bucket(__key); }
#if __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_set> _Kt>
size_type
bucket(const _Kt& __key) const
{ return _M_h._M_bucket_tr(__key); }
#endif
///@}
///@{
/**
* @brief Returns a read-only (constant) iterator pointing to the first
@@ -1884,6 +1907,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
bucket_size(size_type __n) const
{ return _M_h.bucket_size(__n); }
///@{
/*
* @brief Returns the bucket index of a given element.
* @param __key A key instance.
@@ -1893,6 +1917,14 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
bucket(const key_type& __key) const
{ return _M_h.bucket(__key); }
#if __glibcxx_associative_heterogeneous_insertion // C++26
template <__heterogeneous_hash_key<unordered_multiset> _Kt>
size_type
bucket(const _Kt& __key) const
{ return _M_h._M_bucket_tr(__key); }
#endif
///@}
///@{
/**
* @brief Returns a read-only (constant) iterator pointing to the first

View File

@@ -1651,6 +1651,14 @@ ftms = {
};
};
ftms = {
name = associative_heterogeneous_insertion;
values = {
v = 202306;
cxxmin = 26;
};
};
ftms = {
name = is_scoped_enum;
values = {

View File

@@ -1821,6 +1821,16 @@
#endif /* !defined(__cpp_lib_associative_heterogeneous_erasure) */
#undef __glibcxx_want_associative_heterogeneous_erasure
#if !defined(__cpp_lib_associative_heterogeneous_insertion)
# if (__cplusplus > 202302L)
# define __glibcxx_associative_heterogeneous_insertion 202306L
# if defined(__glibcxx_want_all) || defined(__glibcxx_want_associative_heterogeneous_insertion)
# define __cpp_lib_associative_heterogeneous_insertion 202306L
# endif
# endif
#endif /* !defined(__cpp_lib_associative_heterogeneous_insertion) */
#undef __glibcxx_want_associative_heterogeneous_insertion
#if !defined(__cpp_lib_is_scoped_enum)
# if (__cplusplus >= 202100L)
# define __glibcxx_is_scoped_enum 202011L

View File

@@ -80,6 +80,7 @@
#define __glibcxx_want_nonmember_container_access
#define __glibcxx_want_tuple_like
#define __glibcxx_want_associative_heterogeneous_erasure
#define __glibcxx_want_associative_heterogeneous_insertion
#include <bits/version.h>
#if __cplusplus >= 201703L

View File

@@ -78,6 +78,7 @@
#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>
#if __cplusplus >= 201703L

View File

@@ -57,6 +57,7 @@
#define __glibcxx_want_unordered_map_try_emplace
#define __glibcxx_want_tuple_like
#define __glibcxx_want_associative_heterogeneous_erasure
#define __glibcxx_want_associative_heterogeneous_insertion
#include <bits/version.h>
#if __cplusplus >= 201703L

View File

@@ -55,6 +55,7 @@
#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>
#if __cplusplus >= 201703L

View File

@@ -0,0 +1,932 @@
// { dg-do run { target c++26 } }
#include <map>
#include <string>
#include <string_view>
#include <utility>
#include <compare>
#include <cstring>
#include <testsuite_hooks.h>
struct Y;
struct Z;
struct X {
std::string s;
X(std::string_view str) : s(str) {}
X(Y&&);
X(const Y&);
X(Z&&);
X(const Z&);
friend auto operator<=>(X const& a, X const& b) = default;
};
struct Y {
std::string s;
Y() = default;
Y(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
Y(const Y& y) = default;
Y& operator=(Y&& y) { s = std::move(y.s); y.s.clear(); return *this; }
Y& operator=(const Y& y) = default;
Y(std::string_view sv) : s(sv) {}
Y(int n) : s(std::string(n, 'a')) {}
Y(const Y& a, const Y& b) : s(a.s + "1" + b.s) { }
Y(const Y& a, Y&& b) : s(a.s + "2" + b.s) { b.s.clear(); }
Y(Y&& a, const Y& b) : s(a.s + "3" + b.s) { a.s.clear(); }
Y(Y&& a, Y&& b) : s(a.s + "4" + b.s) { a.s.clear(), b.s.clear(); }
friend auto operator<=>(Y const& a, Y const& b) = default;
friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
};
X::X(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
X::X(const Y& y) : s(y.s) {}
using cmp = std::less<void>;
void test_at()
{
std::map<X, Y, cmp> amap{cmp{}};
amap.insert( {{Y{"abc"}, 1},
{Y{"dee"}, 2}, {Y{"def"}, 3}, {Y{"deg"}, 4},
{Y{"ghi"}, 5}});
Y y{"def"};
try
{
VERIFY(3 == amap.at(y));
VERIFY(amap.size() == 5);
VERIFY(4 == (amap.at(std::move(y)) = 4));
VERIFY(amap.size() == 5);
VERIFY(y.s.size() == 3); // not moved from
}
catch(...) { VERIFY(false); }
try
{
amap.at(Y{"dea"}) = 4;
VERIFY(false); // Should have thrown.
}
catch (std::out_of_range&) { VERIFY(amap.size() == 5); }
catch (...) { VERIFY(false); } // Wrong exception.
auto const& amapr{amap};
Y z{"deh"};
try
{
amapr.at(std::move(z));
VERIFY(false); // Should have thrown.
}
catch (std::out_of_range&) { }
catch (...) { VERIFY(false); } // Wrong exception.
VERIFY(amapr.size() == 5);
VERIFY(z.s.size() == 3); // not moved from
}
void test_op_bracket()
{
std::map<X, Y, cmp> amap{cmp{}};
amap.insert({{Y{"abc"}, 1}, {Y{"def"}, 2}, {Y{"ghi"}, 3}});
{
Y z{"deg"};
amap[z] = 4;
VERIFY(amap.size() == 4);
VERIFY(z.s.size() == 3); // pass by value, not moved from.
amap[std::move(z)] = Y{5};
VERIFY(amap.size() == 4);
VERIFY(amap.at(z) == Y{5});
VERIFY(z.s.size() == 3); // already there, not moved from.
}
{
Y y{"deh"};
Y& v = amap[std::move(y)];
VERIFY(v == Y{});
VERIFY(amap.size() == 5);
VERIFY(amap.at(Y{"deh"}) == Y{});
VERIFY(y.s.empty()); // moved from.
}
{
Y x{"dei"};
amap[std::move(x)] = Y{7};
VERIFY(amap.size() == 6);
VERIFY(amap.at(Y{"dei"}) == Y{7});
VERIFY(x.s.empty()); // moved from
}
}
void test_try_emplace()
{
std::map<X, Y, cmp> amap{cmp{}};
amap.insert({{Y{"abc"}, 1}, {Y{"def"}, 2}, {Y{"ghi"}, 3}});
{ // Fail, already there
auto a = amap;
auto [it, res] = a.try_emplace(Y{"def"}, Y{"xyz"});
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(a.at(Y{"def"}) == Y{2}); // unchanged
}
{ // Fail, already there, move
auto a = amap;
Y x{"def"};
Y y{"xyz"};
auto [it, res] = a.try_emplace(std::move(x), std::move(y));
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(a.at(Y{"def"}) == Y{2}); // unchanged
VERIFY(x.s.size() == 3); // not moved from
VERIFY(y.s.size() == 3); // not moved from
}
{ // Succeed, construct
auto a = amap;
{
Y m{"m"}, n{"n"};
auto [it, res] = a.try_emplace(Y{"deg"}, m, n);
VERIFY(res);
VERIFY(a.size() == 4);
VERIFY(it->first == X{"deg"});
VERIFY(it->second == Y{"m1n"});
VERIFY(m.s.size() == 1);
VERIFY(n.s.size() == 1);
}
{
Y m{"m"}, n{"n"};
auto [it, res] = a.try_emplace(Y{"deh"}, m, std::move(n));
VERIFY(res);
VERIFY(a.size() == 5);
VERIFY(it->first == X{"deh"});
VERIFY(it->second == Y{"m2n"});
VERIFY(m.s.size() == 1);
VERIFY(n.s.empty());
}
{
Y m{"m"}, o{"o"};
auto [it, res] = a.try_emplace(Y{"dei"}, std::move(m), o);
VERIFY(res);
VERIFY(a.size() == 6);
VERIFY(it->first == X{"dei"});
VERIFY(it->second == Y{"m3o"});
VERIFY(m.s.empty());
VERIFY(o.s.size() == 1);
}
{
Y o{"o"}, p{"p"};
auto [it, res] = a.try_emplace(Y{"dej"}, std::move(o), std::move(p));
VERIFY(res);
VERIFY(a.size() == 7);
VERIFY(it->first == X{"dej"});
VERIFY(it->second == Y{"o4p"});
VERIFY(o.s.empty());
VERIFY(p.s.empty());
}
{
Y dek{"dek"};
auto [it, res] = a.try_emplace(std::move(dek), Y("q"), Y("r"));
VERIFY(res);
VERIFY(a.size() == 8);
VERIFY(dek.s.empty());
VERIFY(it->first == X{"dek"});
VERIFY(it->second == Y{"q4r"});
}
}
{ // Succeed, move
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto [it, res] = a.try_emplace(std::move(y), std::move(z));
VERIFY(res);
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.empty()); // moved from
VERIFY(z.s.empty()); // moved from
}
{ // Hinted, fail
auto a = amap;
Y y{"def"}, z{"xyz"};
auto it = a.try_emplace(a.begin(), std::move(y), std::move(z));
VERIFY(a.size() == 3);
VERIFY(it->first == X{"def"});
VERIFY(it->second == Y{2});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Hinted, fail, move
auto a = amap;
Y y{"def"}, z{"xyz"};
auto it = a.try_emplace(a.begin(), std::move(y), std::move(z));
VERIFY(a.size() == 3);
VERIFY(it->first == X{"def"});
VERIFY(it->second == Y{2});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Hinted, succeed, construct
auto a = amap;
{
Y m("m"), n("n");
auto it = a.try_emplace(a.begin(), Y{"deg"}, m, n);
VERIFY(a.size() == 4);
VERIFY(it->first == X{"deg"});
VERIFY(it->second == Y{"m1n"});
VERIFY(m.s.size() == 1);
VERIFY(n.s.size() == 1);
}
{
Y m("m"), n("n");
auto it = a.try_emplace(a.begin(), Y{"deh"}, m, std::move(n));
VERIFY(a.size() == 5);
VERIFY(it->first == X{"deh"});
VERIFY(it->second == Y{"m2n"});
VERIFY(m.s.size() == 1);
VERIFY(n.s.empty());
}
{
Y m("m"), o("o");
auto it = a.try_emplace(a.begin(), Y{"dei"}, std::move(m), o);
VERIFY(a.size() == 6);
VERIFY(it->first == X{"dei"});
VERIFY(it->second == Y{"m3o"});
VERIFY(m.s.empty());
VERIFY(o.s.size() == 1);
}
{
Y o("o"), p("p");
auto it = a.try_emplace(a.begin(), Y{"dej"}, std::move(o), std::move(p));
VERIFY(a.size() == 7);
VERIFY(it->first == X{"dej"});
VERIFY(it->second == Y{"o4p"});
VERIFY(o.s.empty());
VERIFY(p.s.empty());
}
{
Y dek("dek");
auto it = a.try_emplace(a.begin(), std::move(dek), Y("q"), Y("r"));
VERIFY(a.size() == 8);
VERIFY(dek.s.empty());
VERIFY(it->first == X{"dek"});
VERIFY(it->second == Y{"q4r"});
}
}
{ // Hinted, succeed, move
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto it = a.try_emplace(a.begin(), std::move(y), std::move(z));
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.empty()); // moved from
VERIFY(z.s.empty()); // moved from
}
}
void test_insert_or_assign()
{
std::map<X, Y, cmp> amap{cmp{}};
amap.insert({{Y{"abc"}, 1}, {Y{"def"}, 2}, {Y{"ghi"}, 3}});
{ // Already there, replace
auto a = amap;
Y y{"def"}, z{"xyz"};
auto [it, res] = a.insert_or_assign(y, z);
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(a.at(Y{"def"}) == Y{"xyz"});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Already there, move
auto a = amap;
Y y{"def"}, z{"xyz"};
auto [it, res] = a.insert_or_assign(std::move(y), std::move(z));
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(a.at(Y{"def"}) == Y{"xyz"});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.empty()); // moved from
}
{ // Succeed, move
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto [it, res] = a.insert_or_assign(std::move(y), std::move(z));
VERIFY(res);
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.empty()); // moved from
VERIFY(z.s.empty()); // moved from
}
{ // Hinted, already there, replace
auto a = amap;
Y y{"def"}, z{"xyz"};
auto it = a.insert_or_assign(a.begin(), y, z);
VERIFY(a.size() == 3);
VERIFY(it->first == X{"def"});
VERIFY(it->second == Y{"xyz"});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Hinted, already there, move
auto a = amap;
Y y{"def"}, z{"xyz"};
auto it = a.insert_or_assign(a.begin(), std::move(y), std::move(z));
VERIFY(a.size() == 3);
VERIFY(it->first == X{"def"});
VERIFY(it->second == Y{"xyz"});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.empty()); // moved from
}
{ // Hinted, succeed
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto it = a.insert_or_assign(a.begin(), y, z);
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Hinted, succeed, move
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto it = a.insert_or_assign(a.begin(), std::move(y), std::move(z));
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.empty()); // moved from
VERIFY(z.s.empty()); // moved from
}
}
struct Z {
std::string s;
mutable int compares = 0;
Z() = default;
Z(Z&& z) : s(std::move(z.s)) { z.s.clear(); }
Z(const Z& z) = default;
Z& operator=(Z&& z) { s = std::move(z.s); z.s.clear(); return *this; }
Z& operator=(const Z& z) = default;
Z(std::string_view sv) : s(sv) {}
Z(int n) : s(std::string(n, 'a')) {}
friend auto operator<=>(Z const& a, Z const& b) = default;
friend auto operator<=>(X const& a, Z const& b)
{ return ++b.compares, a.s.substr(0, b.s.size()) <=> b.s; }
};
X::X(Z&& z) : s(std::move(z.s)) { z.s.clear(); }
X::X(const Z& z) : s(z.s) {}
// A heterogeneous key type like Z here that compares equal
// if it matches just the first part of the key is allowed,
// which affects op[], try_emplace, and insert_or_assign.
auto populate(auto a)
{
const std::string vs[] = { "dec", "ded", "dee", "def", "deg", "deh", "dei" };
for (auto const& v : vs)
a[Y{v}] = Y{v};
return a;
}
void test_op_bracket_prefix()
{
std::map<X, Y, cmp> amap{cmp{}};
amap.insert({{Y{"abc"}, 1}, {Y{"def"}, 2}, {Y{"ghi"}, 3}});
{ // Already there, multiple matches
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"de"};
a[std::move(z)] = Y{5};
VERIFY(a.size() == 9);
VERIFY(a.at(X{"dec"}) == Y{5}); // lower_bound match changed
VERIFY(a.at(X{"ded"}) == Y{"ded"});
VERIFY(a.at(X{"dee"}) == Y{"dee"});
VERIFY(a.at(X{"def"}) == Y{"def"});
VERIFY(a.at(X{"deg"}) == Y{"deg"});
VERIFY(a.at(X{"deh"}) == Y{"deh"});
VERIFY(a.at(X{"dei"}) == Y{"dei"});
VERIFY(z.s.size() == 2); // not moved from
}
}
void test_try_emplace_prefix()
{
std::map<X, Y, cmp> amap{cmp{}};
amap.insert({{Y{"abc"}, 1}, {Y{"def"}, 2}, {Y{"ghi"}, 3}});
{
auto a = populate(amap);
VERIFY(a.size() == 9);
{
Z z{"de"};
auto [it, res] = a.try_emplace(std::move(z), 5);
VERIFY(a.size() == 9);
VERIFY(!res);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{"dec"});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // lower_bound match unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
}
{
Z z{"df"};
auto [it, res] = a.try_emplace(std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(res);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
}
{ // hinted
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"aaa"};
auto it = a.try_emplace(a.begin(), std::move(z), 5);
VERIFY(a.size() == 10);
VERIFY(it->first == X{"aaa"});
VERIFY(it->second == Y{5});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 1);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"de"};
auto it = a.try_emplace(a.find(X{"dec"}), std::move(z), 5);
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{"dec"});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
VERIFY(z.compares == 3);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.begin(), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares > 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(std::next(a.begin()), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares > 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(std::prev(a.end()), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares == 2);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.end(), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares > 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.find(X{"dei"}), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares == 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.find(X{"dec"}), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares > 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
}
{ // hinted
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"de"};
auto it = a.try_emplace(a.find(X{"dec"}), std::move(z), 5);
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{"dec"});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
VERIFY(z.compares == 3);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"de"};
auto it = a.try_emplace(a.begin(), std::move(z), 5);
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{"dec"});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
VERIFY(z.compares > 3);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"de"};
auto it = a.try_emplace(a.find(X{"dei"}), std::move(z), 5);
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{"dec"});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
VERIFY(z.compares > 3);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"de"};
auto it = a.try_emplace(a.find(X{"ghi"}), std::move(z), 5);
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{"dec"});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
VERIFY(z.compares > 3);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"de"};
auto it = a.try_emplace(a.end(), std::move(z), 5);
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{"dec"});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
VERIFY(z.compares > 3);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.begin(), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares > 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(std::next(a.begin()), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares > 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.find(X{"dei"}), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares == 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.find(X{"dec"}), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares > 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(std::prev(a.end()), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares == 2);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.end(), std::move(z), Y{6});
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{6});
VERIFY(z.s.size() == 0); // moved from
VERIFY(z.compares > 3);
auto it2 = a.find(X{"dei"});
VERIFY(it2 != a.end());
++it2;
VERIFY(it2 != a.end());
VERIFY(it2->first == X{"df"});
}
}
}
void test_insert_or_assign_prefix()
{
std::map<X, Y, cmp> amap{cmp{}};
amap.insert({{Y{"abc"}, 1}, {Y{"def"}, 2}, {Y{"ghi"}, 3}});
{ // Already there, replace
{
Z z{"de"};
auto a = populate(amap);
auto [it, res] = a.insert_or_assign(z, Y{5});
VERIFY(!res);
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{5});
VERIFY(a.at(Y{"dec"}) == Y{5}); // lower_bound changed
VERIFY(a.at(Y{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(Y{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(Y{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(Y{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(Y{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(Y{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
}
{
Z z{"de"};
auto a = populate(amap);
auto [it, res] = a.insert_or_assign(std::move(z), Y{5});
VERIFY(!res);
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{5});
VERIFY(a.at(Y{"dec"}) == Y{5}); // lower_bound changed
VERIFY(a.at(Y{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(Y{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(Y{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(Y{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(Y{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(Y{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
}
}
{ // Hinted, already there, replace
{
Z z{"de"};
auto a = populate(amap);
auto it = a.insert_or_assign(a.find(X{"dec"}), z, Y{5});
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{5});
VERIFY(a.at(Y{"dec"}) == Y{5}); // lower_bound changed
VERIFY(a.at(Y{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(Y{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(Y{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(Y{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(Y{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(Y{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
VERIFY(z.compares == 3);
}
{
Z z{"de"};
auto a = populate(amap);
auto it = a.insert_or_assign(a.end(), std::move(z), Y{5});
VERIFY(a.size() == 9);
VERIFY(it->first == X{"dec"});
VERIFY(it->second == Y{5});
VERIFY(a.at(Y{"dec"}) == Y{5}); // lower_bound changed
VERIFY(a.at(Y{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(Y{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(Y{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(Y{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(Y{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(Y{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.size() == 2); // not moved from
VERIFY(z.compares > 3);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.find(X{"dei"}), std::move(z), 5);
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{5});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 3);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"df"};
auto it = a.try_emplace(a.find(X{"ghi"}), std::move(z), 5);
VERIFY(a.size() == 10);
VERIFY(it->first == X{"df"});
VERIFY(it->second == Y{5});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 2);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"jkl"};
auto it = a.try_emplace(a.find(X{"ghi"}), std::move(z), 5);
VERIFY(a.size() == 10);
VERIFY(it->first == X{"jkl"});
VERIFY(it->second == Y{5});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 2);
}
{
auto a = populate(amap);
VERIFY(a.size() == 9);
Z z{"jkl"};
auto it = a.try_emplace(a.end(), std::move(z), 5);
VERIFY(a.size() == 10);
VERIFY(it->first == X{"jkl"});
VERIFY(it->second == Y{5});
VERIFY(a.at(X{"dec"}) == Y{"dec"}); // unchanged
VERIFY(a.at(X{"ded"}) == Y{"ded"}); // unchanged
VERIFY(a.at(X{"dee"}) == Y{"dee"}); // unchanged
VERIFY(a.at(X{"def"}) == Y{"def"}); // unchanged
VERIFY(a.at(X{"deg"}) == Y{"deg"}); // unchanged
VERIFY(a.at(X{"deh"}) == Y{"deh"}); // unchanged
VERIFY(a.at(X{"dei"}) == Y{"dei"}); // unchanged
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 1);
}
}
}
int main()
{
test_at();
test_op_bracket();
test_try_emplace();
test_insert_or_assign();
test_op_bracket_prefix();
test_try_emplace_prefix();
test_insert_or_assign_prefix();
}

View File

@@ -0,0 +1,376 @@
// { dg-do run { target c++26 } }
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <compare>
#include <cstring>
#include <testsuite_hooks.h>
struct Y;
struct Z;
struct X {
std::string s;
X(std::string_view str) : s(str) {}
X(Y&& y);
X(const Y& y);
X(Z&& z);
X(const Z& z);
friend auto operator<=>(X const& a, X const& b) = default;
};
struct Y {
std::string s;
Y() = default;
Y(std::string_view sv) : s(sv) {}
Y(int a, int b = 0) : s(std::string('a', a + b)) {}
Y(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
Y(const Y& y) = default;
Y& operator=(Y&& y) { s = std::move(y.s); y.s.clear(); return *this; }
Y& operator=(const Y& y) = default;
friend auto operator<=>(Y const& a, Y const& b) = default;
friend auto operator<=>(X const& a, Y const& b) { return a.s <=> b.s; }
};
X::X(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
X::X(const Y& y) : s(y.s) {}
using cmp = std::less<void>;
void test_root()
{
std::set<X, cmp> aset{cmp{}};
aset.insert(Y{"def"});
VERIFY(aset.size() == 1);
VERIFY(aset.contains(X{"def"}));
aset.insert(Y{"abc"});
VERIFY(aset.size() == 2);
VERIFY(aset.contains(X{"abc"}));
aset.insert(Y{"ghi"});
VERIFY(aset.size() == 3);
VERIFY(aset.contains(X{"ghi"}));
}
void test_insert()
{
std::set<X, cmp> aset{cmp{}};
aset.insert({Y{"abc"}, Y{"def"}, Y{"ghi"}});
{ // Fail
auto a = aset;
Y y{"def"};
auto [it, res] = a.insert(y);
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(it->s == "def");
VERIFY(y.s.size() == 3); // not moved
}
{ // Fail, move
auto a = aset;
Y y{"def"};
auto [it, res] = a.insert(std::move(y));
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(it->s == "def");
VERIFY(y.s.size() == 3); // not moved
}
{ // Succeed
auto a = aset;
Y y{"deg"};
auto [it, res] = a.insert(y);
VERIFY(res);
VERIFY(a.size() == 4);
VERIFY(it->s == "deg");
VERIFY(y.s.size() == 3); // not moved
}
{ // Succeed, move
auto a = aset;
Y y{"deg"};
auto [it, res] = a.insert(std::move(y));
VERIFY(res);
VERIFY(a.size() == 4);
VERIFY(it->s == "deg");
VERIFY(y.s.empty()); // moved
}
{ // Hinted, fail
auto a = aset;
Y y{"def"};
auto it = a.insert(a.begin(), y);
VERIFY(a.size() == 3);
VERIFY(it->s == "def");
VERIFY(y.s.size() == 3); // not moved
}
{ // Hinted, fail, move
auto a = aset;
Y y{"def"};
auto it = a.insert(a.begin(), std::move(y));
VERIFY(a.size() == 3);
VERIFY(it->s == "def");
VERIFY(y.s.size() == 3); // not moved
}
{ // Hinted, succeed
auto a = aset;
Y y{"deh"};
auto it = a.insert(a.begin(), y);
VERIFY(a.size() == 4);
VERIFY(it->s == "deh");
VERIFY(y.s.size() == 3); // not moved
}
{ // Hinted, succeed, move
auto a = aset;
Y y{"deh"};
auto it = a.insert(a.begin(), std::move(y));
VERIFY(a.size() == 4);
VERIFY(it->s == "deh");
VERIFY(y.s.empty()); // moved
}
}
struct Z {
std::string s;
mutable int compares = 0;
Z() = default;
Z(Z&& z) : s(std::move(z.s)) { z.s.clear(); }
Z(const Z& z) = default;
Z& operator=(Z&& z) { s = std::move(z.s); z.s.clear(); return *this; }
Z& operator=(const Z& z) = default;
Z(std::string_view sv) : s(sv) {}
Z(int n) : s(std::string(n, 'a')) {}
friend auto operator<=>(Z const& a, Z const& b) = default;
friend auto operator<=>(X const& a, Z const& b)
{ return ++b.compares, a.s.substr(0, b.s.size()) <=> b.s; }
};
X::X(Z&& z) : s(std::move(z.s)) { z.s.clear(); }
X::X(const Z& z) : s(z.s) {}
// A heterogeneous key type like Z here that compares equal
// if it matches just the first part of the key is allowed,
// which affects op[], try_emplace, and insert_or_assign.
auto populate(auto a)
{
const std::string vs[] = { "dec", "ded", "dee", "def", "deg", "deh", "dei" };
for (auto const& v : vs)
a.insert(Y{v});
return a;
}
void test_insert_prefix()
{
std::set<X, cmp> aset{cmp{}};
aset.insert({Y{"abc"}, Y{"def"}, Y{"ghi"}});
{
{ // Already there, fail.
Z z{"de"};
auto a = populate(aset);
auto [it, res] = a.insert(std::move(z));
VERIFY(!res);
VERIFY(a.size() == 9);
VERIFY(*it == X{"dec"});
VERIFY(!a.contains(Y{"de"}));
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.size() == 2); // not moved from
VERIFY(z.compares > 3);
}
}
{
{ // hinted, succeed
Z z{"aaa"};
auto a = populate(aset);
auto it = a.insert(a.begin(), std::move(z));
VERIFY(a.size() == 10);
VERIFY(*it == X{"aaa"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 1);
}
}
{ // Hinted, already there, fail.
{
Z z{"de"};
auto a = populate(aset);
auto it = a.insert(a.begin(), std::move(z));
VERIFY(a.size() == 9);
VERIFY(*it == X{"dec"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.size() == 2);
VERIFY(z.compares > 3);
}
{
Z z{"de"};
auto a = populate(aset);
auto it = a.insert(a.find(X{"dec"}), std::move(z));
VERIFY(a.size() == 9);
VERIFY(*it == X{"dec"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.size() == 2);
VERIFY(z.compares == 3);
}
{
Z z{"de"};
auto a = populate(aset);
auto it = a.insert(a.find(X{"def"}), std::move(z));
VERIFY(a.size() == 9);
VERIFY(*it == X{"dec"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.size() == 2);
VERIFY(z.compares > 3);
}
{
Z z{"de"};
auto a = populate(aset);
auto it = a.insert(a.find(X{"dei"}), std::move(z));
VERIFY(a.size() == 9);
VERIFY(*it == X{"dec"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.size() == 2);
VERIFY(z.compares > 3);
}
{
Z z{"df"};
auto a = populate(aset);
auto it = a.insert(a.find(X{"dei"}), std::move(z));
VERIFY(a.size() == 10);
VERIFY(*it == X{"df"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 3);
}
{
Z z{"de"};
auto a = populate(aset);
auto it = a.insert(a.find(X{"ghi"}), std::move(z));
VERIFY(a.size() == 9);
VERIFY(*it == X{"dec"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.size() == 2);
VERIFY(z.compares > 3);
}
{
Z z{"df"};
auto a = populate(aset);
auto it = a.insert(a.find(X{"ghi"}), std::move(z));
VERIFY(a.size() == 10);
VERIFY(*it == X{"df"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 2);
}
{
Z z{"de"};
auto a = populate(aset);
auto it = a.insert(a.end(), std::move(z));
VERIFY(a.size() == 9);
VERIFY(*it == X{"dec"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.size() == 2);
VERIFY(z.compares > 3);
}
{
Z z{"jkl"};
auto a = populate(aset);
auto it = a.insert(a.find(X{"ghi"}), std::move(z));
VERIFY(a.size() == 10);
VERIFY(*it == X{"jkl"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 2);
}
{
Z z{"jkl"};
auto a = populate(aset);
auto it = a.insert(a.end(), std::move(z));
VERIFY(a.size() == 10);
VERIFY(*it == X{"jkl"});
VERIFY(a.contains(Y{"dec"}));
VERIFY(a.contains(Y{"ded"}));
VERIFY(a.contains(Y{"dee"}));
VERIFY(a.contains(Y{"def"}));
VERIFY(a.contains(Y{"deg"}));
VERIFY(a.contains(Y{"deh"}));
VERIFY(a.contains(Y{"dei"}));
VERIFY(z.s.empty()); // moved from
VERIFY(z.compares == 1);
}
}
}
int main()
{
test_root();
test_insert();
test_insert_prefix();
}

View File

@@ -0,0 +1,353 @@
// { dg-do run { target c++26 } }
#include <unordered_map>
#include <string>
#include <string_view>
#include <utility>
#include <functional>
#include <compare>
#include <testsuite_hooks.h>
struct Y;
struct X {
std::string s;
X(std::string_view str) : s(str) {}
X(Y&& y);
X(const Y& y);
friend bool operator==(X const& a, X const& b) = default;
};
struct Y {
std::string s;
Y() = default;
Y(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
Y(const Y& y) = default;
Y& operator=(Y&& y) { s = std::move(y.s); y.s.clear(); return *this; }
Y& operator=(const Y& y) = default;
Y(std::string_view sv) : s(sv) {}
Y(int n) : s(std::string('a', n)) {}
Y(const Y& a, const Y& b) : s(a.s + "1" + b.s) { }
Y(const Y& a, Y&& b) : s(a.s + "2" + b.s) { b.s.clear(); }
Y(Y&& a, const Y& b) : s(a.s + "3" + b.s) { a.s.clear(); }
Y(Y&& a, Y&& b) : s(a.s + "4" + b.s) { a.s.clear(), b.s.clear(); }
friend bool operator==(Y const& a, Y const& b) = default;
friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
};
X::X(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
X::X(const Y& y) : s(y.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 test_op_bracket()
{
std::unordered_map<X, Y, Hash, Equal> amap;
amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
Y x{"dei"}, y{"deh"}, z{"deg"};
amap[z] = 4;
VERIFY(amap.size() == 4);
VERIFY(z.s.size() == 3); // not moved from.
amap[std::move(z)] = 5;
VERIFY(amap.size() == 4);
VERIFY(z.s.size() == 3); // not moved from.
VERIFY(amap[std::move(y)] == Y{});
VERIFY(amap.size() == 5);
VERIFY(y.s.empty()); // moved from.
amap[std::move(x)] = 7;
VERIFY(amap.size() == 6);
VERIFY(x.s.empty()); // moved from
}
void test_at()
{
std::unordered_map<X, Y, Hash, Equal> amap;
amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
Y x{"def"};
try
{
VERIFY(2 == amap.at(x));
VERIFY(amap.size() == 3);
VERIFY(x.s.size() == 3); // not moved from
VERIFY(4 == (amap.at(x) = 4));
VERIFY(amap.size() == 3);
VERIFY(x.s.size() == 3); // not moved from
}
catch(...) { VERIFY(false); }
Y z{"deg"};
try
{
amap.at(z) = 4;
VERIFY(false); // Should have thrown.
}
catch (std::out_of_range&) { VERIFY(amap.size() == 3); }
catch (...) { VERIFY(false); } // Wrong exception.
VERIFY(z.s.size() == 3); // not moved from
Y y{"deh"};
auto const& amapr{amap};
try
{
amapr.at(y);
VERIFY(false); // Should have thrown.
}
catch (std::out_of_range&) { }
catch (...) { VERIFY(false); } // Wrong exception.
VERIFY(amapr.size() == 3);
VERIFY(y.s.size() == 3); // not moved from
}
void test_try_emplace()
{
std::unordered_map<X, Y, Hash, Equal> amap;
amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
{ // Fail, already there
auto a = amap;
auto [it, res] = a.try_emplace(Y{"def"}, Y{"xyz"});
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(a.at(Y{"def"}) == Y{2});
}
{ // Fail, already there, move
auto a = amap;
Y y{"def"}, z{"xyz"};
auto [it, res] = a.try_emplace(std::move(y), std::move(z));
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(a.at(Y{"def"}) == Y{2});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Succeed, construct
auto a = amap;
Y m("m"), n("n"), o("o"), p("p"), dek("dek");
{
auto [it, res] = a.try_emplace(Y{"deg"}, m, n);
VERIFY(res);
VERIFY(a.size() == 4);
VERIFY(it->first == X{"deg"});
VERIFY(it->second == Y{"m1n"});
VERIFY(m.s.size() == 1);
VERIFY(n.s.size() == 1);
}
{
auto [it, res] = a.try_emplace(Y{"deh"}, m, std::move(n));
VERIFY(res);
VERIFY(a.size() == 5);
VERIFY(it->first == X{"deh"});
VERIFY(it->second == Y{"m2n"});
VERIFY(m.s.size() == 1);
VERIFY(n.s.empty());
}
{
auto [it, res] = a.try_emplace(Y{"dei"}, std::move(m), o);
VERIFY(res);
VERIFY(a.size() == 6);
VERIFY(it->first == X{"dei"});
VERIFY(it->second == Y{"m3o"});
VERIFY(m.s.empty());
VERIFY(o.s.size() == 1);
}
{
auto [it, res] = a.try_emplace(Y{"dej"}, std::move(o), std::move(p));
VERIFY(res);
VERIFY(a.size() == 7);
VERIFY(it->first == X{"dej"});
VERIFY(it->second == Y{"o4p"});
VERIFY(o.s.empty());
VERIFY(p.s.empty());
}
{
auto [it, res] = a.try_emplace(std::move(dek), Y("q"), Y("r"));
VERIFY(res);
VERIFY(a.size() == 8);
VERIFY(dek.s.empty());
VERIFY(it->first == X{"dek"});
VERIFY(it->second == Y{"q4r"});
}
}
{ // Succeed, move
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto [it, res] = a.try_emplace(std::move(y), std::move(z));
VERIFY(res);
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.empty()); // moved from
VERIFY(z.s.empty()); // moved from
}
{ // Hinted, fail
auto a = amap;
Y y{"def"}, z{"xyz"};
auto it = a.try_emplace(a.begin(), std::move(y), std::move(z));
VERIFY(a.size() == 3);
VERIFY(it->first == X{"def"});
VERIFY(it->second == Y{2});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Hinted, fail, move
auto a = amap;
Y y{"def"}, z{"xyz"};
auto it = a.try_emplace(a.begin(), std::move(y), std::move(z));
VERIFY(a.size() == 3);
VERIFY(it->first == X{"def"});
VERIFY(it->second == Y{2});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Hinted, succeed, construct
auto a = amap;
Y m("m"), n("n"), o("o"), p("p"), dek("dek");
{
auto it = a.try_emplace(a.begin(), Y{"deg"}, m, n);
VERIFY(a.size() == 4);
VERIFY(it->first == X{"deg"});
VERIFY(it->second == Y{"m1n"});
VERIFY(m.s.size() == 1);
VERIFY(n.s.size() == 1);
}
{
auto it = a.try_emplace(a.begin(), Y{"deh"}, m, std::move(n));
VERIFY(a.size() == 5);
VERIFY(it->first == X{"deh"});
VERIFY(it->second == Y{"m2n"});
VERIFY(m.s.size() == 1);
VERIFY(n.s.empty());
}
{
auto it = a.try_emplace(a.begin(), Y{"dei"}, std::move(m), o);
VERIFY(a.size() == 6);
VERIFY(it->first == X{"dei"});
VERIFY(it->second == Y{"m3o"});
VERIFY(m.s.empty());
VERIFY(o.s.size() == 1);
}
{
auto it = a.try_emplace(a.begin(), Y{"dej"}, std::move(o), std::move(p));
VERIFY(a.size() == 7);
VERIFY(it->first == X{"dej"});
VERIFY(it->second == Y{"o4p"});
VERIFY(o.s.empty());
VERIFY(p.s.empty());
}
{
auto it = a.try_emplace(a.begin(), std::move(dek), Y("q"), Y("r"));
VERIFY(a.size() == 8);
VERIFY(dek.s.empty());
VERIFY(it->first == X{"dek"});
VERIFY(it->second == Y{"q4r"});
}
}
{ // Hinted, succeed, move
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto it = a.try_emplace(a.begin(), std::move(y), std::move(z));
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.empty()); // moved from
VERIFY(z.s.empty()); // moved from
}
}
void test_insert_or_assign()
{
std::unordered_map<X, Y, Hash, Equal> amap;
amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"ghi"}, 3}});
{ // Already there, replace
auto a = amap;
Y y{"def"}, z{"xyz"};
auto [it, res] = a.insert_or_assign(y, z);
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(a.at(Y{"def"}) == Y{"xyz"});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Already there, move
auto a = amap;
Y y{"def"}, z{"xyz"};
auto [it, res] = a.insert_or_assign(std::move(y), std::move(z));
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(a.at(Y{"def"}) == Y{"xyz"});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.empty()); // moved from
}
{ // Succeed, move
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto [it, res] = a.insert_or_assign(std::move(y), std::move(z));
VERIFY(res);
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.empty()); // moved from
VERIFY(z.s.empty()); // moved from
}
{ // Hinted, already there, replace
auto a = amap;
Y y{"def"}, z{"xyz"};
auto it = a.insert_or_assign(a.begin(), y, z);
VERIFY(a.size() == 3);
VERIFY(it->first == X{"def"});
VERIFY(it->second == Y{"xyz"});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Hinted, already there, move
auto a = amap;
Y y{"def"}, z{"xyz"};
auto it = a.insert_or_assign(a.begin(), std::move(y), std::move(z));
VERIFY(a.size() == 3);
VERIFY(it->first == X{"def"});
VERIFY(it->second == Y{"xyz"});
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.empty()); // moved from
}
{ // Hinted, succeed
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto it = a.insert_or_assign(a.begin(), y, z);
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.size() == 3); // not moved from
VERIFY(z.s.size() == 3); // not moved from
}
{ // Hinted, succeed, move
auto a = amap;
Y y{"tuv"}, z{"xyz"};
auto it = a.insert_or_assign(a.begin(), std::move(y), std::move(z));
VERIFY(it->first == X{"tuv"});
VERIFY(it->second == Y{"xyz"});
VERIFY(a.size() == 4);
VERIFY(y.s.empty()); // moved from
VERIFY(z.s.empty()); // moved from
}
}
int main()
{
test_op_bracket();
test_at();
test_try_emplace();
test_insert_or_assign();
}

View File

@@ -0,0 +1,57 @@
// { dg-do run { target c++26 } }
#include <unordered_map>
#include <string>
#include <string_view>
#include <utility>
#include <functional>
#include <testsuite_hooks.h>
struct Y;
struct X {
std::string s;
X(std::string_view str) : s(str) {}
X(Y&& y);
X(const Y& y);
friend bool operator==(X const& a, X const& b) = default;
};
struct Y {
std::string s;
Y() = default;
Y(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
Y(const Y& y) = default;
Y& operator=(Y&& y) { s = std::move(y.s); y.s.clear(); return *this; }
Y& operator=(const Y& y) = default;
Y(std::string_view sv) : s(sv) {}
Y(int a, int b = 0) : s(std::string('a', a + b)) {}
friend bool operator==(Y const& a, Y const& b) = default;
friend bool operator==(X const& a, Y const& b) { return a.s == b.s; }
};
X::X(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
X::X(const Y& y) : s(y.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 test_bucket()
{
std::unordered_multimap<X, Y, Hash, Equal> amap;
amap.insert({{X{"abc"}, 1}, {X{"def"}, 2}, {X{"def"}, 3}, {X{"ghi"}, 3}});
auto const& amapr{amap};
VERIFY(amapr.bucket(X{"def"}) == amapr.bucket(Y{"def"}));
}
int main()
{
test_bucket();
}

View File

@@ -0,0 +1,56 @@
// { dg-do run { target c++26 } }
#include <unordered_set>
#include <string>
#include <string_view>
#include <utility>
#include <functional>
#include <testsuite_hooks.h>
struct Y;
struct X {
std::string s;
X(Y&& y);
X(const Y& y);
X(std::string_view str) : s(str) {}
friend bool operator==(X const& a, X const& b) = default;
};
struct Y {
std::string s;
Y() = default;
Y(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
Y(const Y& y) = default;
Y& operator=(Y&& y) { s = std::move(y.s); y.s.clear(); return *this; }
Y& operator=(const Y& y) = default;
Y(std::string_view 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; }
};
X::X(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
X::X(const Y& y) : s(y.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 test_bucket()
{
std::unordered_multiset<X, Hash, Equal> aset{};
aset.insert({X{"abc"}, X{"def"}, X{"def"}, X{"ghi"}});
auto const& asetr{aset};
VERIFY(asetr.bucket(X{"def"}) == asetr.bucket(Y{"def"}));
}
int main()
{
test_bucket();
}

View File

@@ -0,0 +1,134 @@
// { dg-do run { target c++26 } }
#include <unordered_set>
#include <string>
#include <string_view>
#include <utility>
#include <functional>
#include <testsuite_hooks.h>
struct Y;
struct X {
std::string s;
X(std::string_view str) : s(str) {}
X(Y&& y);
X(const Y& y);
friend bool operator==(X const& a, X const& b) = default;
};
struct Y {
std::string s;
Y() = default;
Y(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
Y(const Y& y) = default;
Y& operator=(Y&& y) { s = std::move(y.s); y.s.clear(); return *this; }
Y& operator=(const Y& y) = default;
Y(int a, int b = 0) : s(std::string('a', a + b)) {}
Y(std::string_view 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; }
};
X::X(Y&& y) : s(std::move(y.s)) { y.s.clear(); }
X::X(const Y& y) : s(y.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 test_insert()
{
std::unordered_set<X, Hash, Equal> aset;
aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
{ // Fail
auto a = aset;
Y y{"def"};
auto [it, res] = a.insert(y);
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(it->s == "def");
VERIFY(y.s.size() == 3); // not moved
}
{ // Fail, move
auto a = aset;
Y y{"def"};
auto [it, res] = a.insert(std::move(y));
VERIFY(!res);
VERIFY(a.size() == 3);
VERIFY(it->s == "def");
VERIFY(y.s.size() == 3); // not moved
}
{ // Succeed
auto a = aset;
Y y{"deg"};
auto [it, res] = a.insert(y);
VERIFY(res);
VERIFY(a.size() == 4);
VERIFY(it->s == "deg");
VERIFY(y.s.size() == 3); // not moved
}
{ // Succeed, move
auto a = aset;
Y y{"deg"};
auto [it, res] = a.insert(std::move(y));
VERIFY(res);
VERIFY(a.size() == 4);
VERIFY(it->s == "deg");
VERIFY(y.s.empty()); // moved
}
{ // Hinted, fail
auto a = aset;
Y y{"def"};
auto it = a.insert(a.begin(), y);
VERIFY(a.size() == 3);
VERIFY(it->s == "def");
VERIFY(y.s.size() == 3); // not moved
}
{ // Hinted, fail, move
auto a = aset;
Y y{"def"};
auto it = a.insert(a.begin(), std::move(y));
VERIFY(a.size() == 3);
VERIFY(it->s == "def");
VERIFY(y.s.size() == 3); // not moved
}
{ // Hinted, succeed
auto a = aset;
Y y{"deh"};
auto it = a.insert(a.begin(), y);
VERIFY(a.size() == 4);
VERIFY(it->s == "deh");
VERIFY(y.s.size() == 3); // not moved
}
{ // Hinted, succeed, move
auto a = aset;
Y y{"deh"};
auto it = a.insert(a.begin(), std::move(y));
VERIFY(a.size() == 4);
VERIFY(it->s == "deh");
VERIFY(y.s.empty()); // moved
}
}
void test_bucket()
{
std::unordered_set<X, Hash, Equal> aset{};
aset.insert({X{"abc"}, X{"def"}, X{"ghi"}});
auto const& asetr{aset};
VERIFY(asetr.bucket(X{"def"}) == asetr.bucket(Y{"def"}));
}
int main()
{
test_insert();
test_bucket();
}