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

*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 が利用可能、ということ。おわり。

## コード例

```cpp: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)](http://melpon.org/wandbox/permlink/KzSq0hRixwKvVOZp) ので絶望的っぽい。

**追記 (2015/10/14)**：gcc HEAD 6.0.0 20151013 (experimental)
では[動くようになっていた (Wandbox)](http://melpon.org/wandbox/permlink/1LuYmAyaJY9eCvlj)。

# C++11 の場合

## 結論

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

## コード例

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

```cpp
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)](http://www.slideshare.net/faithandbrave/c14-enum-hash) の実装例ほぼそのまま（元コードは [[llvm-project] Contents of /libcxx/trunk/include/functional](http://llvm.org/viewvc/llvm-project/libcxx/trunk/include/functional?view=markup) の末尾の方にある）。

## コンパイル例

### g++ on Linux

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

# 参考

* [C++14 LWG.2148 列挙型のハッシュサポート](http://www.slideshare.net/faithandbrave/c14-enum-hash)
* [std::hash - cppreference.com](http://en.cppreference.com/w/cpp/utility/hash)
* [hash (C++11) - cpprefjp C++日本語リファレンス](http://cpprefjp.github.io/reference/functional/hash.html)

# 旧記事

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

iorate さんの tweet や [Standard library header &lt;type_traits&gt; - cppreference.com](http://en.cppreference.com/w/cpp/header/type_traits) を参考にしています。

```cpp
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>>;
```