unorderd_map のキーに enum 型を使用する
新年明けましておめでとうございます。去年はちっとも儲からなかったので、今年は本腰入れて開発やって自力で稼げる事業を立ち上げたく、その準備を進めて参る所存でございます。どうぞ生暖かく見守っていただければと思います…。
さて、前回の記事でお見せした、 iconv のラッパークラスをテンプレートクラスに作り直す際、 <unorderd_map>
を利用していて気づいたことの備忘録です。
問題
テンプレート引数に、内部文字列の (アラインメントを決定する) 物理型と文字セットを指定できるようにしたかったのですが、文字セットを示す文字列そのものをテンプレート引数に渡すことはできないので、代わりに文字セットを表す列挙値を定義し、対応する「文字セットを表す文字列」と関連づけた連想配列を <unorderd_map>
を用いて用意する、ということをやってみることにしました。 C/C++ の列挙値は int
にキャスト可能なので昔の自分だったら単純に「文字セットを表す文字列」の配列を用意して添え字代わりに列挙値を突っ込むところですが、メンテナンス性良くないですし、ハッシュテーブルなら検索コストは (ほぼ) 変わらないですからね。
で、ヘッダファイルの方で列挙型を定義しーの、
enum charset_t { chset_Utf8, chset_C99, chset_Java, chset_Ucs2, chset_Ucs2Be, chset_Ucs2Le, chset_Ucs4, chset_Ucs4Be, chset_Ucs4Le, chset_Utf16, chset_Utf16Be, chset_Utf16Le, chset_Utf32, chset_Utf32Be, chset_Utf32Le, chset_Utf7, chset_EucJp, chset_EucJis0213, chset_Iso2022Jp, chset_Iso2022Jp2, chset_Iso2022Jp1, chset_Iso2022Jp3, chset_ShiftJis, chset_Cp932, chset_ShiftJisX0213, };
テンプレート化しない実装部分のクラスに static
で const
な連想配列メンバを追加しーの、
class EncodeStringImpl { typedef std::unordered_map<charset_t, char const *> cnmap_t; static cnmap_t const CharsetNames; // ... };
実装ファイルの方で連想配列の値を定義しーの、
EncodeStringImpl::cnmap_t const EncodeStringImpl::CharsetNames = { { chset_Utf8, "UTF-8" }, { chset_C99, "C99" }, { chset_Java, "JAVA" }, { chset_Ucs2, "UCS-2" }, { chset_Ucs2Be, "UCS-2BE" }, { chset_Ucs2Le, "UCS-2LE" }, { chset_Ucs4, "UCS-4" }, { chset_Ucs4Be, "UCS-4BE" }, { chset_Ucs4Le, "UCS-4LE" }, { chset_Utf16, "UTF-16" }, { chset_Utf16Be, "UTF-16BE" }, { chset_Utf16Le, "UTF-16LE" }, { chset_Utf32, "UTF-32" }, { chset_Utf32Be, "UTF-32BE" }, { chset_Utf32Le, "UTF-32LE" }, { chset_Utf7, "UTF-7" }, { chset_EucJp, "EUC-JP" }, { chset_EucJis0213, "EUC-JISX0213" }, { chset_Iso2022Jp, "ISO-2022-JP" }, { chset_Iso2022Jp2, "ISO-2022-JP2" }, { chset_Iso2022Jp1, "ISO-2022-JP1" }, { chset_Iso2022Jp3, "ISO-2022-JP3" }, { chset_ShiftJis, "SHIFT_JIS" }, { chset_Cp932, "CP932" }, { chset_ShiftJisX0213, "SHIFT_JISX0213" }, };
実際に使いーの、とやってみたはよいのですが、
void EncodeStringImpl::encode(void const *src, size_t src_length, size_t chr_size, charset_t from_charset, charset_t to_charset) { // ... class auto_iconv_t { // 生成時に iconv_open してスコープ抜ける時に iconv_close するプライベートクラス… const iconv_t impl; public: auto_iconv_t(charset_t from_cs, charset_t to_cs) : // ↓こことか impl(iconv_open(CharsetNames.find(to_cs)->second, CharsetNames.find(from_cs)->second)) { if (impl == reinterpret_cast<iconv_t>(-1)) { // ↓こことか throw EncodeStringException(string("LIBICONV initialize error: please check character set name \"") + CharsetNames.find(from_cs)->second + "\"(from) or \"" + CharsetNames.find(to_cs)->second + "\"(to)"); } } ~auto_iconv_t() { iconv_close(impl); } iconv_t get() const { return impl; } } iconv_handle(from_charset, to_charset);
いざ g++
してみると、「
なんて定義されてねーよ」とリンカ様に怒られてしまいました。std::hash<charset_t>::operator()(charset_t) const
murachi@ubuntu-vbox:~/otoco/trunk/sample$ g++ -std=c++0x -o kumapan EncodeString.cpp kumapan.cpp /tmp/ccIn30PX.o: In function `std::__detail::_Hash_code_base<charset_t, std::pair<charset_t const, char const*>, std::_Select1st<std::pair<charset_t const, char const*> >, std::equal_to<charset_t>, std::hash<charset_t>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_hash_code(charset_t const&) const': EncodeString.cpp:(.text._ZNKSt8__detail15_Hash_code_baseI9charset_tSt4pairIKS1_PKcESt10_Select1stIS6_ESt8equal_toIS1_ESt4hashIS1_ENS_18_Mod_range_hashingENS_20_Default_ranged_hashELb0EE12_M_hash_codeERS3_[std::__detail::_Hash_code_base<charset_t, std::pair<charset_t const, char const*>, std::_Select1st<std::pair<charset_t const, char const*> >, std::equal_to<charset_t>, std::hash<charset_t>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_hash_code(charset_t const&) const]+0x19): undefined reference to `std::hash<charset_t>::operator()(charset_t) const' /tmp/ccIn30PX.o: In function `std::__detail::_Hash_code_base<charset_t, std::pair<charset_t const, char const*>, std::_Select1st<std::pair<charset_t const, char const*> >, std::equal_to<charset_t>, std::hash<charset_t>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_bucket_index(std::__detail::_Hash_node<std::pair<charset_t const, char const*>, false> const*, unsigned int) const': EncodeString.cpp:(.text._ZNKSt8__detail15_Hash_code_baseI9charset_tSt4pairIKS1_PKcESt10_Select1stIS6_ESt8equal_toIS1_ESt4hashIS1_ENS_18_Mod_range_hashingENS_20_Default_ranged_hashELb0EE15_M_bucket_indexEPKNS_10_Hash_nodeIS6_Lb0EEEj[std::__detail::_Hash_code_base<charset_t, std::pair<charset_t const, char const*>, std::_Select1st<std::pair<charset_t const, char const*> >, std::equal_to<charset_t>, std::hash<charset_t>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_bucket_index(std::__detail::_Hash_node<std::pair<charset_t const, char const*>, false> const*, unsigned int) const]+0x28): undefined reference to `std::hash<charset_t>::operator()(charset_t) const' collect2: ld はステータス 1 で終了しました murachi@ubuntu-vbox:~/otoco/trunk/sample$
解決策
ヘッダーファイルの方で、以下の記述を列挙型の定義のすぐ後辺りに追加してやることで、リンクも通るようになります。テンプレートの特殊化ってやつですね。
namespace std { template <> inline size_t hash<charset_t>::operator()(charset_t val) const { return static_cast<size_t>(val); } }
解説
std::hash
テンプレートクラスの実装は、 libstdc++ の場合、ヘッダファイルが置かれる然るべきディレクトリ配下の bits/functional_hash.h
に記述されています。ここでは、非ポインタ型 T
に対する operator()
は (宣言はされているものの) 定義されていません。
/// Primary class template hash. template<typename _Tp> struct hash : public __hash_base<size_t, _Tp> { size_t operator()(_Tp __val) const; }; /// Partial specializations for pointer types. template<typename _Tp> struct hash<_Tp*> : public __hash_base<size_t, _Tp*> { size_t operator()(_Tp* __p) const { return reinterpret_cast<size_t>(__p); } };
で、この operator()
の実装は、ごくごく基本的な組み込み型に対してのみ特殊化されています (ここは流石に長くなるので引用しませんが…)。
こうした実装は C++11 のドラフトにも明記されている標準の仕様に則ったものです。「20.8.12 Class template hash」に以下のような記述があります。
- The unordered associative containers defined in 23.5 use specializations of the class template hash as the default hash function. For all object types Key for which there exists a specialization hash<Key>, the instantiation hash<Key> shall:
- satisfy the Hash requirements (17.6.3.4), with Key as the function call argument type, the DefaultConstructible requirements (Table 19), the CopyAssignable requirements (Table 23),
- be swappable (17.6.3.2) for lvalues,
- provide two nested types result_type and argument_type which shall be synonyms for size_t and Key, respectively,
- satisfy the requirement that if k1 == k2 is true, h(k1) == h(k2) is also true, where h is an object of type hash<Key> and k1 and k2 are objects of type Key.
template <> struct hash<bool>; template <> struct hash<char>; template <> struct hash<signed char>; template <> struct hash<unsigned char>; template <> struct hash<char16_t>; template <> struct hash<char32_t>; template <> struct hash<wchar_t>; template <> struct hash<short>; template <> struct hash<unsigned short>; template <> struct hash<int>; template <> struct hash<unsigned int>; template <> struct hash<long>; template <> struct hash<unsigned long>; template <> struct hash<long long>; template <> struct hash<unsigned long long>; template <> struct hash<float>; template <> struct hash<double>; template <> struct hash<long double>; template <class T> struct hash<T*>;
そんな訳で、デフォルトで std::hash
を利用する std::unordered_map
を enum
型をキーにして使用したい場合には、その enum
型で std::hash::operator()
を特殊化してあげる必要があるのです。
2012 年 1 月 2 日 by 村山 俊之