Specializing std::hash to derived classes
Asked Answered
H

1

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?

Hydrogenate answered 20/2, 2014 at 7:10 Comment(3)
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 specialisationPyle
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<Hashable> for all it's derived types. This is why i dislike traits like templates rather than functions through adl.Messuage
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.Hydrogenate
H
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);
Hydrogenate answered 20/2, 2014 at 17:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.