Question

BACKGROUND

I am trying to write a class template Hasher which will be implemented in two different ways depending on whether or not std::hash<T> has been implemented for T:

template<typename T>
struct Hasher
{
  std::size_t hash( T t ) const;
  // implement as A { std::hash<T> h; return h( t ); }
  //           or B { std::hash<std::string> h; return h( t.to_string() );  }
};

If std::hash<T> has been specialised, I want to use it. If not, I expect T to have a to_string() function to return a key for me to hash on.

For example, according to cppreference, if T is long long, a pointer, or std::string, I want version A. If it is not one of those standard ones listed and if the user has not specialised std::hash<T> for his own type, I expect T to have a std::string to_string() const for me to call. In this case, I want to generate version B.

PROBLEM

How do I use C++11/type_traits/no-SFINAE to generate the proper implementation?

ADDENDUM

Another way to think about it:

It's almost like I want version B to be the default behavior (ie, if no specialization exists, to use version B).

TESTED NAWAZ'S SOLUTION

I just tried out Nawaz's solution on gcc 4.8.1 as his came in first and is actually the easiest for me to read and understand (more important).

#include <functional>
#include <cassert>

template<typename T>
class Hasher
{
        // overloading rules will select this one first...  ...unless it's not valid
        template<typename U>
        static auto hash_impl(U const& u, int)
                -> decltype(std::hash<U>().operator()( u ))
        {
                 return std::hash<U>().operator()( u );
        }
        // as a fallback, we will pick this one
        template<typename U>
        static auto hash_impl(U const& u, ... )
                -> std::size_t
        {
                 return std::hash<std::string>().operator()(u.to_string());
        }

public:
        auto hash( T t ) const -> decltype( hash_impl(t,0) )
        {
                 return hash_impl( t, 0 );
        }
};

struct Foo
{
        std::string  m_id;
        std::string  to_string() const { return m_id; }
};

int
main( int argc, char** argv )
{
        std::string        s{ "Bar" };
        Foo                f{ s     };
        long long          l{ 42ll  };

        Hasher<long long>   hl;
        Hasher<Foo>         hf;
        Hasher<std::string> hs;

        assert( hl.hash( l )==l );
        assert( hf.hash( f )==hs.hash( s ));

        return 0;
}

TESTED DANIEL FREY'S SOLUTION

Daniel's implementation is also very interesting. By computing first whether or not we have a hash, I am able to use tag-dispatch to select the implementation I want. We have a nice pattern/separation-of-concerns which leads to very clean code.

However, in the implementation of has_hash<>, the arguments to decltype confused me at first. In fact, it shouldn't read as arguments. Rather, it is a list of expressions (comma separated expressions). We need to follow the rules as expressed here.

C++ ensures that each of the expressions is evaluated and its side effects take place. However, the value of an entire comma-separated expression is only the result of the rightmost expression.

Also, the use of void() was a mystery to me at first. When I changed it to double() to see what would happen, it was clear why it really should be void() (so we don't need to pass in that second template parameter).

#include <functional>
#include <cassert>

template< typename, typename = void >
struct has_hash
  : std::false_type {};

template< typename T >
struct has_hash< T, decltype( std::hash< T >()( std::declval< T >() ), void() ) >
  : std::true_type {};

template<typename T>
class Hasher
{
        static std::size_t hash_impl(T const& t, std::true_type::type )
        {
                 return std::hash<T>().operator()( t );
        }

        static std::size_t hash_impl(T const& t, std::false_type::type )
        {
                 return std::hash<std::string>().operator()(t.to_string());
        }

public:
        std::size_t hash( T t ) const
        {
                 return hash_impl( t, typename has_hash<T>::type() );
        }
};

struct Foo
{
        std::string  m_id;
        std::string  to_string() const { return m_id; }
};

int
main( int argc, char** argv )
{
        std::string        s{ "Bar" };
        Foo                f{ s     };
        long long          l{ 42ll  };

        Hasher<long long>   hl;
        Hasher<Foo>         hf;
        Hasher<std::string> hs;

        assert( hl.hash( l )==l );
        assert( hf.hash( f )==hs.hash( s ));

        return 0;
}
Was it helpful?

Solution

You could use Expression SFINAE introduced by C++11.

Here is one example how it is implemented:

template<typename T>
struct Hasher
{
    auto hash( T t ) const -> decltype(hash_impl(t,0))
    {
       return hash_impl(t, 0);
    }
  private:
    template<typename U>
    static auto hash_impl(U const & u, int) -> decltype(std::hash<U>().hash(u))
    {
       return std::hash<U>().hash(u);
    }
    template<typename U>
    static auto hash_impl(U const & u, ... ) -> std::string
    {
       return u.to_string();
    }
};

Note that hash_impl is an overloaded function template. So when you write this:

       return hash_impl(t, 0); 

Since the second argument 0 is int, the above first attempts to invoke hash_impl which uses std::hash — this attempt might fail if std::hash<U>().hash(u) is not a valid expression (Expression SFINAE). If that fails, then the second hash_impl is invoked.

OTHER TIPS

You could just test if std::hash<T>()(...) can be called with type in question. If so, you'll get back a type from decltype() which can be used in a SFINAE expression to determine the size of a return type:

template <typename T>
struct has_hash
{
    template <typename S>
    static char (&test(S*, decltype(std::hash<S>()(std::declval<T&>()))* = 0))[1];
    static char (&test(...))[2];
    static constexpr bool value = 1 == sizeof(test(static_cast<T*>(0)));
};

Base on this you could then use the has_hash<T>::value to determine if there is a usable hash function already defined.

If you need to test if an expression is valid or not, I prefer the following implementation:

template< typename, typename = void >
struct has_hash
  : std::false_type {};

template< typename T >
struct has_hash< T, decltype( std::hash< T >()( std::declval< T >() ), void() ) >
//                            ^^ expression here ------------------^^
  : std::true_type {};

Live example

Yet another implementation

First, some boilerplate. type_sink and TypeSink let us evaluate types, and discard them.

template<typename... T> struct type_sink {typedef void type;};
template<typename... T> using TypeSink = typename type_sink<T>::type;

We then write a really simple has_hash that uses SFINAE. The default choice is "no hash", and the specialization is valid iff std::hash<T>()( t ) is a valid expression for a variable t of type T:

template<typename T, typename=void> struct has_hash:std::false_type {};
template<typename T> struct has_hash<
  T, TypeSink< decltype( std::hash<T>()( std::declval<T&>() ) ) >
>: std::true_type {}; 

we then write our universal hasher:

template<typename T>
struct Hasher
{
private:
  typedef has_hash< typename std::decay<T>::type > test_for_hash;
public:
  std::size_t operator()( T t ) const {
    return hash( std::forward<T>(t), test_for_hash() );
  }
private:
  std::size_t hash( T t, std::true_type has_hash ) const {
    return std::hash<T>()( std::forward<T>(t) );
  }
  std::size_t hash( T t, std::false_type no_hash ) const {
    // TODO: static_assert that t.to_string exists, and if not give a useful error message
    return std::hash<std::string>()( std::forward<T>(t).to_string() )
  }
};

where we use the has_hash trait to do tagged dispatching to either the "use hash directly" or "to_string then hash".

We can do more layers of this dispatching.

I took the liberty of making it somewhat move-aware, in that T can be T& or T const& or T and it behaves reasonably. (I don't know about T&& off the top of my head).

As noted in other comments, some work to generate better error messages can be done. We want the compiler's error message to complain about the lack of a hash<T> implementation rather than a to_string implementation probably. That would involve writing a has_to_string traits class and either doing another layer of tag dispatching, or doing a static_assert to generate a useful message telling the end user to implement either the hash<T> specialization or T::to_string.

Another option would be to make a universal hasher:

template<typename T>
struct Hasher
{
private:
  typedef has_hash< T > test_for_hash;
public:
  template<typename U>
  std::size_t operator()( U&& u ) const {
    return hash( std::forward<U>(u), test_for_hash() );
  }
private:
  template<typename U>
  std::size_t hash( U&& u, std::true_type has_hash ) const {
    return std::hash<T>()( std::forward<U>(u) ); // conversion occurs here
  }
  template<typename U>
  std::size_t hash( U&& u, std::false_type no_hash ) const {
    // TODO: static_assert that t.to_string exists, and if not give a useful error message
    T t = std::forward<U>(u); // conversion occurs here -- note, implicit on purpose!
    return std::hash<std::string>()( std::move(t).to_string() )
  }
};

which defers transformation-to-T until the last moment.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top