Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::conditional vs std::enable_if

Tags:

c++

c++14

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?

like image 860
Hatted Rooster Avatar asked Feb 02 '16 11:02

Hatted Rooster


People also ask

What is std :: Enable_if?

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.

How does STD conditional work?

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.


2 Answers

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>>;  
};
like image 63
TartanLlama Avatar answered Oct 18 '22 19:10

TartanLlama


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.

like image 29
T.C. Avatar answered Oct 18 '22 20:10

T.C.