Edited at

連想配列 (std::unordered_map) のキーに enum class を使う

More than 3 years have passed since last update.

2015/05/22 全面改訂

std::unordered_map のキーとして enum class (スコープ付き enum)の値を使いたい。


C++14 の場合


結論

規格的には何もしなくても ok。

実装的には clang++ & libc++ なら多分 ok。g++ はバージョン 4 系や 5 系ではコンパイルできないっぽいので「C++11 の場合」を参照してください。


解説

C++14 規格の作業草案 (N3936) の "20.9.12 Class template hash [unord.hash]" の項に以下のように記載されている。


For all object types Key for which there exists a specialization hash<Key>, and for all enumeration types (7.2) Key, the instantiation hash<Key> shall:

(以下省略)


つまり、std::hash にスコープ付き enum 用の特殊化が存在 → std::unordered_map のキーとしてスコープ付き enum が利用可能、ということ。おわり。


コード例


hash_enum_class.cc

#include <iostream>

#include <unordered_map>

enum class Element { H = 1, He, Li, Be };

int main()
{
std::unordered_map<Element, double> dict = {
{Element::H, 1.0}, {Element::He, 4.0},
{Element::Li, 7.0}, {Element::Be, 9.0}
};
for (auto && item : dict) {
std::cout << static_cast<int>(item.first) << ": "
<< item.second << '\n';
}
}



実行例

$ ./hash_enum_class

4: 9
3: 7
2: 4
1: 1

(もちろん unordered なので出力順序は不定。)


コンパイル例


clang++ on OS X

$ clang++ --version

Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
...
$ clang++ -std=c++14 hash_enum_class.cc -o hash_enum_class

-std=c++14 フラグを付ける。


clang++ on Linux

ディストリビューションは Arch Linux。

$ clang++ --version

clang version 3.6.0 (tags/RELEASE_360/final)
...
$ clang++ -std=c++14 -stdlib=libc++ -lc++abi hash_enum_class.cc -o hash_enum_class

libc++ をインストールした状態で -stdlib=libc++ -lc++abi フラグが必要。


g++

Arch Linux 公式レポジトリの 4.9、自前ビルドの 5.1 で動かず、6.0 HEAD でもダメらしい (Wandbox) ので絶望的っぽい。

追記 (2015/10/14):gcc HEAD 6.0.0 20151013 (experimental)

では動くようになっていた (Wandbox)


C++11 の場合


結論

std::hash の特殊化を自分で行う。


コード例

C++14 版コードの #include 行直後に以下のコードを挿入する。

namespace std {

template <class T>
struct hash {
static_assert(is_enum<T>::value, "This hash only works for enumeration types");
size_t operator()(T x) const noexcept {
using type = typename underlying_type<T>::type;
return hash<type>{}(static_cast<type>(x));
}
};
}

C++14 LWG.2148 列挙型のハッシュサポート (slideshare) の実装例ほぼそのまま(元コードは [llvm-project] Contents of /libcxx/trunk/include/functional の末尾の方にある)。


コンパイル例


g++ on Linux

$ g++ --version

g++ (GCC) 4.9.2 20150304 (prerelease)
...
$ g++ -std=c++11 hash_enum_class.cc -o hash_enum_class


参考


旧記事

以下は std::hash を特殊化するのではなく std::unordered_map のテンプレート引数に自前実装の hash (MyHash) を渡す例です(型はstd::unordered_map<Element, double, MyHash<Element>> のようになります)。enable_if の利用例として残しておきます。

iorate さんの tweet や Standard library header <type_traits> - cppreference.com を参考にしています。

template<class T, class Enable = void>

struct MyHash
{
std::size_t operator()(const T &x) const
{
return std::hash<T>()(x);
}
};

template<class T>
struct MyHash<T, typename std::enable_if<std::is_enum<T>::value>::type>
{
std::size_t operator()(const T &x) const
{
using Underlying = typename std::underlying_type<T>::type;
return std::hash<Underlying>()(static_cast<Underlying>(x));
}
};

template<class K, class V>
using MyUnorderedMap = std::unordered_map<K, V, MyHash<K>>;