Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check if type is hashable

Tags:

I would like to make a type trait for checking if a particular type is hashable using the default instantiations of the standard library's unordered containers, thus if it has a valid specialization for std::hash. I think this would be a very useful feature (e.g. for using std::set as failsafe for std::unordered_set in generic code). So I, thinking std::hash is not defined for each type, started making the following SFINAE solution:

template<typename T> std::true_type hashable_helper(
    const T&, const typename std::hash<T>::argument_type* = nullptr);

template<typename T> std::false_type hashable_helper(...);

//It won't let me derive from decltype directly, why?
template<typename T> struct is_hashable 
    : std::is_same<decltype(hashable_helper<T>(std::declval<T>())),
                   std::true_type> {};

(Forgive my modest SFINAE-abilities if this is not the best solution or even wrong.)

But then I learned, that both gcc 4.7 and VC++ 2012 define std::hash for any type T, just static_asserting in the non-specialized version. But instead of compiling conditionally they (and also clang 3.1 using gcc 4.7's libstdc++) fail the assertion resulting in a compile error. This seems reasonable since I think static_asserts are not handled by SFINAE (right?), so an SFINAE solution seems not possibly at all. It's even worse for gcc 4.6 which doesn't even have a static_assert in the general std::hash template but just doesn't define its () operator, resulting in a linker error when trying to use it (which is always worse than a compile error and I cannot imagine any way to transform a linker error into a compiler error).

So is there any standard-conformant and portable way to define such a type trait returning if a type has a valid std::hash specialization, or maybe at least for the libraries static_asserting in the general template (somehow transforming the static_assert error into a SFINAE non-error)?

like image 898
Christian Rau Avatar asked Oct 05 '12 21:10

Christian Rau


People also ask

What data type is hashable?

Hashable data types: int , float , str , tuple , and NoneType . Unhashable data types: dict , list , and set .

How do I know if a Python item is hashable?

An object is hashable if it has a hash value that does not change during its entire lifetime. Python has a built-in hash method ( __hash__() ) that can be compared to other objects. For comparing it needs __eq__() or __cmp__() method and if the hashable objects are equal then they have the same hash value.

Which Python objects are hashable?

In Python, any immutable object (such as an integer, boolean, string, tuple) is hashable, meaning its value does not change during its lifetime. This allows Python to create a unique hash value to identify it, which can be used by dictionaries to track unique keys and sets to track unique values.

Is list is hashable?

A hashable object can be used as a key for a dictionary or as an element in a set. All built-in immutable objects, like tuples, are hashable while mutable containers like lists and dictionaries are not hashable.


Video Answer


2 Answers

Since C++17 it is now possible to do this in a more elegant way. From cppreference about std::hash:

Each specialization of this template is either enabled ("untainted") or disabled ("poisoned"). For every type Key for which neither the library nor the user provides an enabled specialization std::hash, that specialization exists and is disabled. Disabled specializations do not satisfy Hash, do not satisfy FunctionObject, and std::is_default_constructible_v, std::is_copy_constructible_v, std::is_move_constructible_v, std::is_copy_assignable_v, std::is_move_assignable_v are all false. In other words, they exist, but cannot be used.

This meant that the STL had to remove the static_assert in C++17. Here is a working solution with 'Clang-6.0.0 -std=c++17':

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

template <typename T, typename = std::void_t<>>
struct is_std_hashable : std::false_type { };

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

template <typename T>
constexpr bool is_std_hashable_v = is_std_hashable<T>::value; 

struct NotHashable {};

int main()
{
    std::cout << std::boolalpha;
    std::cout << is_std_hashable_v<int> << std::endl;
    std::cout << is_std_hashable_v<NotHashable> << std::endl;
    return 0;
}

This might for example come in handy when you use boost::hash_combine or boost::hash_range. If you include a header containing the following code sample you do not need to define boost hashes for specific types anymore.

#include <boost/functional/hash_fwd.hpp>

template <typename T, typename = std::void_t<>>
struct is_boost_hashable : std::false_type { };

template <typename T>
struct is_boost_hashable<T, std::void_t<decltype(boost::hash_value(std::declval<T>()))>> : std::true_type { };

template <typename T>
constexpr bool is_boost_hashable_v = is_boost_hashable<T>::value;  

namespace boost
{
    template <typename T>
    auto hash_value(const T &arg) -> std::enable_if_t<is_std_hashable_v<T> &&
                                                      !is_boost_hashable_v<T>, std::size_t>
    {
        return std::hash<T>{}(arg);
    }
}

Notice the is_boost_hashable_v, this is necessary to avoid ambiguity as boost already provides hashes for a lot of hashes.

like image 52
Mattias De Charleroy Avatar answered Oct 14 '22 13:10

Mattias De Charleroy


It seems we have two conflicting requirements:

  1. SFINAE is meant to avoid any instantiation of a template if the instantiation might fail and remove the corresponding function from the overload set.
  2. static_assert() is meant to create an error, e.g., during instantiation of a template.

To my mind, 1. clearly trumps 2., i.e., your SFINAE should work. From the looks of two separate compiler vendors disagree, unfortunately not between themselves but with me. The standard doesn't seem to specify how the default definition of std::hash<T> looks like and seems to impose constraints only for the cases where std::hash<T> is specialized for a type T.

I think your proposed type traits is a reasonable idea and it should be supported. However, it seems the standard doesn't guarantee that it can be implemented. It may be worth bringing this up with the compiler vendors and/or filing a defect report for the standard: The current specification doesn't give clear guidance what should happen, as far as I can tell. ... and if the specification currently mandates that a type traits as above fails it may be a design error which needs to be corrected.

like image 23
Dietmar Kühl Avatar answered Oct 14 '22 13:10

Dietmar Kühl