9

I have an abstract base class Hashable that classes that can be hashed derive from. I would now like to extend std::hash to all classes that derive from Hashable.

The following code is supposed to do exactly that.

#include <functional>
#include <type_traits>
#include <iostream>

class Hashable {
public:
    virtual ~Hashable() {}
    virtual std::size_t Hash() const =0;
};

class Derived : public Hashable {
public:
    std::size_t Hash() const {
        return 0;
    }
};

// Specialization of std::hash to operate on Hashable or any class derived from
// Hashable.
namespace std {
template<class C>
struct hash {
  typename std::enable_if<std::is_base_of<Hashable, C>::value, std::size_t>::type
  operator()(const C& object) const {
    return object.Hash();
  }
};
}

int main(int, char**) {
    std::hash<Derived> hasher;
    Derived d;
    std::cout << hasher(d) << std::endl;

    return 0;
}

The above code works exactly as expected with gcc 4.8.1, but when I try to compile it with gcc 4.7.2, I get the following:

$ g++ -std=c++11 -o test test_hash.cpp
test_hash.cpp:22:8: error: redefinition of ‘struct std::hash<_Tp>’
In file included from /usr/include/c++/4.7/functional:59:0,
                 from test_hash.cpp:1:
/usr/include/c++/4.7/bits/functional_hash.h:58:12: error: previous definition of ‘struct std::hash<_Tp>’
/usr/include/c++/4.7/bits/functional_hash.h: In instantiation of ‘struct  std::hash<Derived>’:
test_hash.cpp:31:24:   required from here
/usr/include/c++/4.7/bits/functional_hash.h:60:7: error: static assertion failed:  std::hash is not specialized for this type

Can anybody think of a way to make this specialization of std::hash work for any class derived from Hashable with gcc 4.7.2?

Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90
Flecto
  • 315
  • 2
  • 7
  • 1
    Er no that code is broken. You're not allowed to declare new templates in `std`. You can only write specialisations of existing templates and only under certain conditions. What you have there is not a specialisation – R. Martinho Fernandes Feb 20 '14 at 13:10
  • 2
    Even if we ignore the rules about what you are and are not allowed to do in the std namespace, your class is a redefinition which is not allowed in any context and even if it was allowed, using it would be ambiguous since both definitions would match. You are going to have to either specialize for each type or only for `Hashable` and just use `std::hash` for all it's derived types. This is why i dislike traits like templates rather than functions through adl. – John5342 Feb 20 '14 at 13:23
  • Thank you for your comments. I suspected that my solution was not quite kosher and I was actually surprised that it worked at all in gcc 4.8.1. I have decided to write separate specializations for each derived class. – Flecto Feb 20 '14 at 17:54

1 Answers1

7

It seems like there is no proper way to do what I wanted to do. I have decided to just write separate specializations for each derived class, using the following macro:

// macro to conveniently define specialization for a class derived from Hashable
#define DEFINE_STD_HASH_SPECIALIZATION(hashable)                               \
namespace std {                                                                \
template<>                                                                     \
struct hash<hashable> {                                                        \
  std::size_t operator()(const hashable& object) const {                       \
    return object.Hash();                                                      \
  }                                                                            \
};                                                                             \
}

and then

// specialization of std::hash for Derived
DEFINE_STD_HASH_SPECIALIZATION(Derived);
Flecto
  • 315
  • 2
  • 7