Search code examples
c++hashstructunordered-set

Hashing function for nested struct in struct when using an unordered_set


I'm trying to use an unordered_set to maintain an unique list of structs. I've defined the hash function for the struct, Name, however I receive compile errors when I extend the Name struct to contain another struct member, Address. I know I need to specify how the Address struct must be hashed, but I can't seem to figure out where/how.

#include <unordered_set>
#include <string>

using namespace std;

struct Address
{
    int num;
};

struct Name
{
    string first;
    string second;
    Address address;
};


struct hashing_fn {

    size_t operator()(const Address &a ) const
    {
        return  hash<int>()(a.num);
    }

    size_t operator()(const Name &name ) const
    {
        return hash<string>()(name.first) ^ hash<string>()(name.second) ^ hash<Address>()(name.address);
    }
};

int main(int argc, char* argv[])
{
    unordered_set<Name,hashing_fn> ids;
    return 0;
}

Update

Just for completion, this was the fix:

template<>
struct hash<typename Address> {
    size_t operator()(const Address &a ) const
    {
        return  hash<int>()(a.num);
    }
};

Solution

  • You never defined hash<Address>! Instead, you have to use your own function operator()(name.address).


    Simple XORing is maybe not the best solution. I strongly recommend you copy over Boost's hash_combine(), and put the whole load into namespace std:

    template <class T>
    inline void hash_combine(std::size_t & seed, const T & v)
    {
      std::hash<T> hasher;
      seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
    
    namespace std
    {
      template <> struct hash<Address>
      {
        inline size_t operator()(const Address & a) const
        {
          return hash<int>()(a.num);
        }
      };
    
      template <> struct hash<Name>
      {
        inline size_t operator()(const Name & a) const
        {
          size_t seed = 0;
          hash_combine(seed, name.first);
          hash_combine(seed, name.second);
          hash_combine(seed, name.address);
          return seed;
        }
      };
    }
    

    Now you can use the types directly (subject to implementing an equality comparator): std::unordered_set<Name> s;