LoginSignup
10
6

More than 5 years have passed since last update.

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

Last updated at Posted at 2012-12-26

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>>;
10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6