C++標準バージョン毎のhas_iterator 実装方法と解説
汎用オレオレライブラリ製作などに当たって標準コンテナに適応できるようiterator型に対する型特性を扱いたい時というのもまあまあ頻度としてある気がするので、既出ネタであるような気もするがC++03、11、14、1z毎のhas_iterator type traitsメタ関数の実装等を紹介してみる。
C++03準拠
template<class _Tp> class has_iterator{ typedef char enable_t; typedef struct{char d[2];}disable_t; template<class _U> static enable_t t(typename _U::iterator*); template<class _U> static disable_t t(...); public: static const bool value=sizeof(t<_Tp>(0))==sizeof(enable_t); };
enable_t
、disable_t
というような、型のサイズがそれぞれで異なる型を定義しそれを戻り値として設定してenable_t
と同サイズか判定する事で、SFINAEの結果をbool値に起こす。SFINAEを用いるため、enable_t
とdisable_t
は非関数テンプレートで宣言する事はできない。sizeof式内での記述は評価されず戻り型サイズが取得できるため関数の実態は必要ない。
C++03準拠であればこの実装は効果的である。
C++11準拠
decltype
式*1を用いる事ができるので、以下のように実装できる。
struct has_iterator_impl{ template<class _Tp> static std::true_type t(typename _Tp::iterator*); template<class _Tp> static std::false_type t(...); }; template<class _Tp> struct has_iterator:decltype(has_iterator_impl::t<_Tp>(nullptr)){};
has_iterator
の継承文でt
という静的メンバ関数のSFINAEによってiterator型を所持しているか判断しその結果によって継承先が変動するため、その継承先のvalue
をユーザーに指定してもらう事でbool値を受け取る事ができる。同じくdecltype式内での記述は評価されず、戻り型が取得できるため関数の実態は必要ない。
C++11ではvariadic template*2とTemplates Aliases*3が使えるため、自前で以下のようなクラスを実装する場合は、別の実装法もある。
template<class...> using void_t=void;
このクラスを用いて、以下のように実装できる。*4
template<class,class=void_t<>> struct has_iterator:std::false_type{}; template<class _Tp> struct has_iterator<_Tp,void_t<typename _Tp::iterator>>:std::true_type{};
void_t
は何の型をどれだけ渡してもvoidを返すため、その中に一定の条件で有効になる式を好きに記述する事ができる。ここでは、iterator
を持つ_Tp
とし、その判定結果によるSFINAEを実行させている。
C++14準拠
Variable Templates*5を用いる事ができるので、以下のように記述できる。
template<class...> using void_t=void; template<class,class=void_t<>> constexpr bool has_iterator_v=false; template<class _Tp> constexpr bool has_iterator_v<_Tp,void_t<typename _Tp::iterator>> =true;
C++1z(17)準拠
前述したvoid_tは、C++1zでは標準ライブラリとなる予定(執筆時段階ではdraftのため)*6。
template<class,class=std::void_t<>> struct has_iterator:std::false_type{}; template<class _Tp> struct has_iterator<_Tp,std::void_t<typename _Tp::iterator>>:std::true_type{};
余談 標準ライブラリに準ずるiteratorか
iterator
を単純に持つかという点では以上のtype traits関数で用件を満たしているが、「標準ライブラリの」もしくは「標準ライブラリに準ずる」iterator
を持つかどうかを判断するためには、もう少し手間を加える必要があるだろう。
template<class...> using void_t=void; template<class,class=void_t<>> struct has_std_iterator:std::false_type{}; template<class _Tp> struct has_std_iterator<_Tp,void_t<typename _Tp::iterator::iterator_category>>:std::true_type{}; int main() { struct has_dummy_iterator_type{struct iterator{};}; static_assert(has_std_iterator<std::vector<int>>::value); static_assert(!has_std_iterator<has_dummy_iterator_type>::value); }
便宜上iterator_category
を標準の条件としているが、より厳密な条件を課せられるのであれば是非とも適応すべきである。
*1:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3276.pdf
*2:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2555.pdf
*3:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2258.pdf
*4:http://roki.hateblo.jp/entry/2016/09/06/void_t%E3%82%92%E7%94%A8%E3%81%84%E3%81%9Fdetection_idiom
*5:http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3651.pdf
*6:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3911.pdf