c++stlg++unordered-set

How can I use a lightweight argument in unordered_set::find() method?


Must I fully build a Storage object for finding it, instead of using std::string because of having operator== for it?

#include <cstddef>
#include <functional>
#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::string key;
  // One more fat fields...
};

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

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

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

  // Variant #1 - works
  auto it = uset.find({ .key="42" /* and init other fields */ });

  // Variant #2 - doesn't compiles :(
  auto it = uset.find(std::string("42"));
}

I tried operator std::string() for struct Storage, and I tried different variants of Storage::operator==() like this:

bool operator==(const Storage& lhs, const std::string& rhs)

bool operator==(const std::string& lhs, const Storage& rhs)

But it doesn't work.


Solution

    1. your Hash and Equality need to have using is_transparent = std::true_type;
    2. your Hash needs to be able to hash both the Storage type and your find argument type.
    struct Storage
    {
      struct Hash
      {
        std::size_t operator()(const Storage& storage) const noexcept
        {
          return std::hash<std::string>{}(storage.key);
        }
    
        std::size_t operator()(std::string_view key) const noexcept
        {
            return std::hash<std::string_view>{}(key);
        }
        using is_transparent = std::true_type;
      };
    
      std::string key;
      // One more fat fields...
    };
    
    auto uset = std::unordered_set<Storage, Storage::Hash, std::equal_to<>>
    

    online demo

    you should replace std::string with std::string_view in both the hash and Equality to avoid the unnecessary copy, it is guaranteed to have the same hash as std::string, see the comment.

    PS: don't forget inline on your free functions to avoid multiple definitions.


    also note that this is now ambigious on MSVC

    auto it = uset.find({ .key="42" });
    

    as now find can take either std::string_view or Storage, you need to explicitly type it

    auto it = uset.find(Storage{ .key="42" });