Jukka Suomela
Jukka Suomela

Reputation: 12338

Use boost::hash_value to define std::hash in C++11

Is there an easy way to do the following with C++11 & Boost:

For example:

Upvotes: 4

Views: 6677

Answers (2)

user405725
user405725

Reputation:

The first idea that comes to mind is to use SFINAE and try std::hash<> if possible and otherwise use boost::hash_value(), like this:

#include <string>
#include <functional>
#include <type_traits>
#include <boost/functional/hash.hpp>

struct my_struct_0 {
    std::string s;
};

template <typename T>
struct has_std_hash_subst { typedef void type; };

template <typename T, typename C = void>
struct has_std_hash : std::false_type {};

template <typename T>
struct has_std_hash<
    T,
    typename has_std_hash_subst<decltype( std::hash<T>()(T()) ) >::type
> : std::true_type {};

template <typename T>
static typename std::enable_if<has_std_hash<T>::value, size_t>::type
make_hash(const T &v)
{
    return std::hash<T>()(v);
}

template <typename T>
static typename std::enable_if<(!has_std_hash<T>::value), size_t>::type
make_hash(const T &v)
{
    return boost::hash_value(v);
}

int main()
{
    make_hash(std::string("Hello, World!"));
    make_hash(my_struct_0({ "Hello, World!" }));
}

Unfortunately, there is always a default specialization of std::hash that triggers static_assert failure. This may not be the case with other libraries but it is the case with GCC 4.7.2 (see bits/functional_hash.h:60):

  /// Primary class template hash.
  template<typename _Tp>
    struct hash : public __hash_base<size_t, _Tp>
    {
      static_assert(sizeof(_Tp) < 0,
                    "std::hash is not specialized for this type");
      size_t operator()(const _Tp&) const noexcept;
    };

So the above SFINAE approach doesn't work — static_assert in there is a show-stopper. Therefore, you cannot really determine when std::hash is available.

Now, this does not really answer your question but might come handy — it is possible to do this trick the other way around — check for Boost implementation first and only then fall back to std::hash<>. Consider the below example that uses boost::hash_value() if it is available (i.e. for std::string and my_struct_0) and otherwise uses std::hash<> (i.e. for my_struct_1):

#include <string>
#include <functional>
#include <type_traits>
#include <boost/functional/hash.hpp>

struct my_struct_0 {
    std::string s;
};

struct my_struct_1 {
    std::string s;
};

namespace boost {
size_t hash_value(const my_struct_0 &v) {
    return boost::hash_value(v.s);
}
}

namespace std {
template <>
struct hash<my_struct_1> {
    size_t operator()(const my_struct_1 &v) const {
        return std::hash<std::string>()(v.s);
    }
};

}

template <typename T>
struct has_boost_hash_subst { typedef void type; };

template <typename T, typename C = void>
struct has_boost_hash : std::false_type {};

template <typename T>
struct has_boost_hash<
    T,
    typename has_boost_hash_subst<decltype(boost::hash_value(T()))>::type
> : std::true_type {};

template <typename T>
static typename std::enable_if<has_boost_hash<T>::value, size_t>::type
make_hash(const T &v)
{
    size_t ret = boost::hash_value(v);
    std::cout << "boost::hash_value(" << typeid(T).name()
              << ") = " << ret << '\n';
    return ret;
}

template <typename T>
static typename std::enable_if<(!has_boost_hash<T>::value), size_t>::type
make_hash(const T &v)
{
    size_t ret = std::hash<T>()(v);
    std::cout << "std::hash(" << typeid(T).name()
              << ") = " << ret << '\n';
    return ret;
}

int main()
{
    make_hash(std::string("Hello, World!"));
    make_hash(my_struct_0({ "Hello, World!" }));
    make_hash(my_struct_1({ "Hello, World!" }));
}

Hope it helps.

UPDATE: Perhaps you could use the hack described here as pointed out by @ChristianRau and make the first SFINAE approach work! Though it is very dirty :)

Upvotes: 5

Marcin Deptuła
Marcin Deptuła

Reputation: 11957

My answer might not be correct, but I will try to explain why I think that the answer is no.

I don't think that std::hash<T> and boost:hash<T> can be used interchangeably, so I've tried hiding object creation (even if this is not perfect solution), and return their result, which is size_t. Method should be of course chosen at compile time, so function dispatch is what comes to my mind, sample code:

template <typename T>
size_t createHash(const T& t, false_type)
{
    return boost::hash<T>()(t);
}

template <typename T>
size_t createHash(const T& t, true_type)
{   
    return std::hash<T>()(t);
}

template<typename T>
size_t createHash(const T& t)
{
    return createHash<T>(t, std::is_XXX<T>::type());
}


int main() 
{
    vector<unsigned> v; v.push_back(1);
    auto h1 = createHash(v);
    cout << " hash: " << h1;
    //hash<vector<unsigned> > h2;
}

The idea of this code is simple: if you can construct type of type std::hash<T>, choose second implementation, if not - choose first one.

If the first implementation is chosen, code compiles without a problem, you can check it by using fe. std::is_array<T>::type() in a wrapper function, which is of course not true, so boost::hash implementation will be choosed. However, if you use a trait which will return true_t for a vector<unsigned>, like fe. std::is_class<T>::type() then the compiler will report "C++ Standard doesn't provide...", which is a result of a static_assert.

For this to work, we would need to force compiler to return true_t if a type is really constructible (it doesn't fail static_assert) and false_t if it doesn't. However, I don't think there is a possibility to do that.

Upvotes: 1

Related Questions