Reputation: 36483
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?
Upvotes: 16
Views: 4244
Reputation: 137320
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.
Upvotes: 8
Reputation: 30605
Both types (branches) used in the conditional
need to be well formed when they are evaluated at compile time.
The issue is that when the compiler parses the std::conditional
itself, the statement typename std::underlying_type<T>::type
needs to be well formed (and it is not for the int
). It doesn't really matter what the result is yet, it hasn't got that far.
Upvotes: 2
Reputation: 65610
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>>;
};
Upvotes: 13
Reputation: 5729
typename std::underlying_type<T>::type
This is not well-formed for an int
, but it is still evaluated at compile time. That's why you're getting this error. You already gave a solution using std::enable_if
, so I'm not go into details there. I think it is fine and using std::conditional
would just make it harder to read anyway.
Upvotes: 2