I have a hashing function that can take any object type and hash it, it uses std::hash
internally. Because std::hash
does not support enum types I've created overloads of the function, 1 for enumerations using std::underlying_type
and 1 for other types:
template <typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
static std::size_t Hash(T const & t)
{
return std::hash<typename std::underlying_type<T>::type>()(t);
}
template <typename T, typename std::enable_if<!std::is_enum<T>::value>::type* = nullptr>
static std::size_t Hash(T const & t)
{
return std::hash<T>()(t);
}
This is working fine.
I then tried to put it all into a single function with std::conditional
:
template <typename T>
static std::size_t Hash(T const & t)
{
typedef typename std::conditional<std::is_enum<T>::value, std::hash<typename std::underlying_type<T>::type>, std::hash<T>>::type Hasher;
return Hasher()(t);
}
Main:
enum test
{
TEST = 2
};
int main() {
Hash<test>(TEST);
Hash<int>(5);
std::cin.get();
return 0;
}
This however, gave me an error :
/usr/include/c++/5/type_traits:2190:38: error: 'int' is not an enumeration type typedef __underlying_type(_Tp) type;
I do understand the error but I don't understand why, I thought std::conditional
would prevent these compile-time errors because <int>
uses std::hash<T>
instead of std::hash<typename std::underlying_type<T>::type>
at compile-time.
What am I doing wrong here, is there a way I can merge the 2 functions?
std::enable_if can be used in many forms, including: as an additional function argument (not applicable to operator overloads) as a return type (not applicable to constructors and destructors) as a class template or function template parameter.
std::conditionalProvides member typedef type , which is defined as T if B is true at compile time, or as F if B is false. The behavior of a program that adds specializations for conditional is undefined.
The problem is that std::underlying_type
is not well-formed if the template argument is not an enum, and both branches of std::conditional
need to be valid.
One possibility would be to make a safe_underlying_type
trait which just returns void
if T
is not an enum:
template <typename T, typename = typename std::is_enum<T>::type>
struct safe_underlying_type {
using type = void;
};
template <typename T>
struct safe_underlying_type<T, std::true_type> {
using type = std::underlying_type_t<T>;
};
Or you could just write a trait to get the hash type you want:
template <typename T, typename = typename std::is_enum<T>::type>
struct hash_type {
using type = std::hash<T>;
};
template <typename T>
struct hash_type<T, std::true_type> {
using type = std::hash<std::underlying_type_t<T>>;
};
If you really want to use conditional
, you'll need to defer evaluation:
template<class T> struct identity { using type = T; };
using Hasher = std::hash<typename std::conditional<std::is_enum<T>::value,
std::underlying_type<T>,
identity<T>>::type::type>;
Also, std::hash
should natively support enums since LWG 2148.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With