Rokiのチラ裏

学生による学習のログ

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_tdisable_tというような、型のサイズがそれぞれで異なる型を定義しそれを戻り値として設定してenable_tと同サイズか判定する事で、SFINAEの結果をbool値に起こす。SFINAEを用いるため、enable_tdisable_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を標準の条件としているが、より厳密な条件を課せられるのであれば是非とも適応すべきである。