c++g++c++23unordered-set

Must I fully build an unordered_set::key_type object to call the erase() method?


I asked the similar question for find() method of std::unordered_set last day - How can I use a lightweight argument in unordered_set::find() method?. But the recommendations received for find() method doesn't work for erase() method.

#include <cstddef>
#include <functional>
#include <print>
#include <string>
#include <unordered_set>

struct Storage
{
  struct Hash
  {
    std::size_t operator()(const Storage& storage) const noexcept
    {
      return std::hash<std::string>{}(storage.key);
    }
    std::size_t operator()(const std::string& key) const noexcept
    {
        return std::hash<std::string>{}(key);
    }
    using is_transparent = std::true_type;
  };

  std::string key;
  // One more fat fields...
};

inline bool
operator==(const Storage& lhs, const Storage& rhs) noexcept
{
  return lhs.key == rhs.key;
}

inline bool
operator==(const Storage& lhs, const std::string& rhs) noexcept
{
  return lhs.key == rhs;
}

inline bool
operator==(const std::string& lhs, const Storage& rhs) noexcept
{
  return lhs == rhs.key;
}

int
main()
{
  auto uset = std::unordered_set<Storage, Storage::Hash, std::equal_to<>>
  {
    { .key="42" }
  };

  // It's compiled here
  auto it = uset.find("42");
  if (it != uset.end())
    std::println("{}", it->key);

  // But doesn't compile here :(
  uset.erase("42");
}

cppreference indicates that the following overload is present:

template< class K >
size_type erase( K&& x );

I found it here - https://en.cppreference.com/w/cpp/container/unordered_set/erase.html. But g++ told me this:

main.cc: In function ‘int main()’:
main.cc:58:13: error: no matching function for call to ‘std::unordered_set<Storage, Storage::Hash, std::equal_to<void> >::erase(const char [3])’
   58 |   uset.erase("42");
      |   ~~~~~~~~~~^~~~~~
main.cc:58:13: note: there are 4 candidates
In file included from /usr/include/c++/15.1.1/unordered_set:43,
                 from main.cc:5:
/usr/include/c++/15.1.1/bits/unordered_set.h:594:7: note: candidate 1: ‘std::unordered_set<_Value, _Hash, _Pred, _Alloc>::iterator std::unordered_set<_Value, _Hash, _Pred, _Alloc>::erase(const_iterator) [with _Value = Storage; _Hash = Storage::Hash; _Pred = std::equal_to<void>; _Alloc = std::allocator<Storage>; iterator = std::_Hashtable<Storage, Storage, std::allocator<Storage>, std::__detail::_Identity, std::equal_to<void>, Storage::Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, true, true> >::iterator; const_iterator = std::_Hashtable<Storage, Storage, std::allocator<Storage>, std::__detail::_Identity, std::equal_to<void>, Storage::Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, true, true> >::const_iterator]’
  594 |       erase(const_iterator __position)
      |       ^~~~~
/usr/include/c++/15.1.1/bits/unordered_set.h:594:28: note: no known conversion for argument 1 from ‘const char [3]’ to ‘std::unordered_set<Storage, Storage::Hash, std::equal_to<void> >::const_iterator’ {aka ‘std::_Hashtable<Storage, Storage, std::allocator<Storage>, std::__detail::_Identity, std::equal_to<void>, Storage::Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, true, true> >::const_iterator’}
  594 |       erase(const_iterator __position)
      |             ~~~~~~~~~~~~~~~^~~~~~~~~~
/usr/include/c++/15.1.1/bits/unordered_set.h:599:7: note: candidate 2: ‘std::unordered_set<_Value, _Hash, _Pred, _Alloc>::iterator std::unordered_set<_Value, _Hash, _Pred, _Alloc>::erase(iterator) [with _Value = Storage; _Hash = Storage::Hash; _Pred = std::equal_to<void>; _Alloc = std::allocator<Storage>; iterator = std::_Hashtable<Storage, Storage, std::allocator<Storage>, std::__detail::_Identity, std::equal_to<void>, Storage::Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, true, true> >::iterator]’
  599 |       erase(iterator __position)
      |       ^~~~~
/usr/include/c++/15.1.1/bits/unordered_set.h:599:22: note: no known conversion for argument 1 from ‘const char [3]’ to ‘std::unordered_set<Storage, Storage::Hash, std::equal_to<void> >::iterator’ {aka ‘std::_Hashtable<Storage, Storage, std::allocator<Storage>, std::__detail::_Identity, std::equal_to<void>, Storage::Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, true, true> >::iterator’}
  599 |       erase(iterator __position)
      |             ~~~~~~~~~^~~~~~~~~~
/usr/include/c++/15.1.1/bits/unordered_set.h:616:7: note: candidate 3: ‘std::unordered_set<_Value, _Hash, _Pred, _Alloc>::size_type std::unordered_set<_Value, _Hash, _Pred, _Alloc>::erase(const key_type&) [with _Value = Storage; _Hash = Storage::Hash; _Pred = std::equal_to<void>; _Alloc = std::allocator<Storage>; size_type = long unsigned int; key_type = Storage]’
  616 |       erase(const key_type& __x)
      |       ^~~~~
/usr/include/c++/15.1.1/bits/unordered_set.h:616:29: note: no known conversion for argument 1 from ‘const char [3]’ to ‘const std::unordered_set<Storage, Storage::Hash, std::equal_to<void> >::key_type&’ {aka ‘const Storage&’}
  616 |       erase(const key_type& __x)
      |             ~~~~~~~~~~~~~~~~^~~
/usr/include/c++/15.1.1/bits/unordered_set.h:634:7: note: candidate 4: ‘std::unordered_set<_Value, _Hash, _Pred, _Alloc>::iterator std::unordered_set<_Value, _Hash, _Pred, _Alloc>::erase(const_iterator, const_iterator) [with _Value = Storage; _Hash = Storage::Hash; _Pred = std::equal_to<void>; _Alloc = std::allocator<Storage>; iterator = std::_Hashtable<Storage, Storage, std::allocator<Storage>, std::__detail::_Identity, std::equal_to<void>, Storage::Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, true, true> >::iterator; const_iterator = std::_Hashtable<Storage, Storage, std::allocator<Storage>, std::__detail::_Identity, std::equal_to<void>, Storage::Hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, true, true> >::const_iterator]’
  634 |       erase(const_iterator __first, const_iterator __last)
      |       ^~~~~
/usr/include/c++/15.1.1/bits/unordered_set.h:634:7: note: candidate expects 2 arguments, 1 provided

Solution

  • Your code is correct, however the overload you are looking for is just quite new and has not yet been adopted by the major compilers (it works for the latest MSVC, though). Since you can still use the transparent comparison to find the iterator, just use it for erasure:

    if (const auto it = uset.find("42"); it != uset.end()) {
        uset.erase(it);
    }