Search code examples
c++hashnestedunordered-set

Custom hash and equivalance function for nested unordered_set of custom class


I have a struct MyStruct which I have an unordered_set for which has it's own hashing function MyStructHash so that I can define unordered_set<MyStruct, MyStructHash> struct_set;.

MyStruct is something like:

struct MyStruct{
  int a;
  double c;
  std::string b;

  std::string toString()...
}

The hash function is:

struct MyStructHash{
    std::size_t operator()(const MyStruct& ms) const{
        return std::hash<std::string>()(ms.toString());
    }
};

I then want to define an unordered_set<unordered_set<MyStruct>> to stop there from being duplicates of the sets. I have treid a hash function like so:

struct MyStructHashSetHash{
  size_t operator()(const std::unordered_set<MyStruct>& set) const{
    size_t hash_val = 0;
    for (const MyStructHash& obj: set){
      hash_val ^= AbstractionHash{}(obj);
    }
    return hash_val;
  }
};

I also have a similar equivalence function. Then I try to define my unordered_set as unordered_set<unordered_set<MyStruct, MyStructHash>, MyStructHashSetHash, MyStructSetEqual> struct_set_set;

However, when I do this I get the error:

/usr/include/c++/11/bits/hashtable_policy.h:1614:23: error: static assertion failed: key equality predicate must be invocable with two arguments of key type
 1614 |         static_assert(__is_invocable<const _Equal&, const _Key&, const _Key&>{},
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I can't find much about this error online. I see that there is an issue with the key, and that you must define a hash function for whatever it is you want an unordered_set for. But that is what I thought I had done with MyStructHashSetHash.

Any help would be great. Thanks!


Solution

  • When I want to make a family of nested hashes, I use ADL.

    namespace myns{
    
    
      namespace adl{
        template<class T>
        std::size_t customhash(T const& t){
          return ::std::hash<T>{}(t);
        }
      }
      template<class T>
      std::size_t autohash(T const&t){
        using adl::customhash;
        return customhash(t);
      }
      struct hasher {
        template<class T>
        std::size_t operator()(T const& t) const {
          return autohash(t);
        }
      };
    }
    

    this is the basic framework.

    You can use ::myns::hasher as a hashing object in place of std::hash<T>.

    To extend it, you add customhash overloads to either the namespace of your type, or myns::adl. As you cannot add overloads to std, to support hashing of unordered maps simply do:

    namespace myns::adl{
      template<class...Ts>
      std::size_t customhash(std::unordered_map<Ts...> cobst& um ){
        // has the unordered map
        // to hash elements simply call customhash(element)
      }
    }
    

    To support hashing your struct, do the same - overload customhash.

    And now recursive struct nested in unordered maps of unordered maps works.

    (If you need to call customhash from outside myns::adl instead call myns::autohash, or do the using trick.)