How to use type_traits to generate code dependent on if a class specialisation exists?

Viewed 1299

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;
}
4 Answers
Related