1

I have a wrapper class for std::string that serves as base class for several others. Instances of the subclasses will be used as keys in std::unordered_set so I need to provide a hash function for them. Since the hash is only dependent on the std::string stored in the base class, I do not want to write a hash function for every subclass but rather use the one from the wrapper class.

This is how I would like to solve the problem:

#include <string>
#include <unordered_set>

class Wrapper {
public:
  std::string name;
  size_t _hash;
  explicit Wrapper(std::string str) : name(str), _hash(std::hash<std::string>()(name)) {}
  size_t hash() const { return _hash; }
};

class Derived : public Wrapper {};

namespace std {

template <> struct hash<Wrapper> {
  std::size_t operator()(const Wrapper &k) const { return k.hash(); }
};

template <typename T> struct hash<std::enable_if_t<std::is_base_of_v<Wrapper, T>>> {
  std::size_t operator()(const T &k) const { return k.hash(); }
};

} // namespace std

int main(void) {
  std::unordered_set<Wrapper> m1;
  std::unordered_set<Derived> m2;
}

This does not compile of course, since T cannot be deduced. Clang says:

20:30: error: class template partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used
20:20: note: non-deducible template parameter 'T'

And g++ says:

hash_subclass.cpp:21:30: error: template parameters not deducible in partial specialization:
 template <typename T> struct hash<std::enable_if_t<std::is_base_of_v<Wrapper, T>>> {
                              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
hash_subclass.cpp:21:30: note:         'T'

I have found this solution, but I would like to avoid using a macro. Also, this goes against what I expect from inheritance.

Is there a solution for this? Can a subclass inherit its base class' specialization of std::hash?

Also, I'm not 100% sure about my use of std::enable_if and std::is_base_of. Could you tell me whether this would work assuming T could be deduced?

1 Answers1

1

IRC, the problem with std::enable_if is that it does not work for classes with a single template parameter. Consequently, you cannot specialize std::hash by using std::enable_if.

However, you can make your own hasher as follows:

template <typename T, typename Enable = std::enable_if_t<std::is_base_of_v<Wrapper, T>>>
struct WrapperHasher {
   std::size_t operator()(const T& k) const { return k.hash(); }
};

And then use it as a second template argument of std::unordered_set:

std::unordered_set<Wrapper, WrapperHasher<Wrapper>> m1;
std::unordered_set<Derived, WrapperHasher<Derived>> m2;

But in your case, you can define a wrapper much more simply as:

struct WrapperHasher {
   std::size_t operator()(const Wrapper& k) const { return k.hash(); }
};

And then write:

std::unordered_set<Wrapper, WrapperHasher> m1;
std::unordered_set<Derived, WrapperHasher> m2;
Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • Hm.... so there is no way I can use `std::unordered_set m1;` without creating a template specialization explicitly for `Derived`? I'm hesitant about polluting the interface by forcing users to carry around the extra hasher. – Sebastian Schmitz Oct 16 '18 at 13:31
  • @Sebastian _"Forcing users"_ indicates that you are writing some API. In that case, I would definitely prefer to manually specialize `std::hash` for all derived classes. How many such classes you have? – Daniel Langr Oct 17 '18 at 05:36
  • Four and there will probably not be any more, so it's not that big of deal. – Sebastian Schmitz Oct 17 '18 at 06:23