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 specializationhash<Key>
, and for all enumeration types (7.2)Key
, the instantiationhash<Key>
shall:
(以下省略)
つまり、std::hash
にスコープ付き enum 用の特殊化が存在 → std::unordered_map
のキーとしてスコープ付き enum が利用可能、ということ。おわり。
コード例
#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>>;