ぷろみん

プログラミング的な内容を扱ってます

自分で作ったクラスをrange based forに対応させよう

Range based forは素敵

for(auto value: vector){
}

良いですね、スマートです。
しかし、これstd::vector等のスタンダードライブラリに入っているクラスしか使えないと思ってはいないでしょうか?
私が体験した職場で自身のクラスをrange based forに対応させているパターンを見たことがありません。
案外簡単にできるので是非やってみてください。

最低限必要なこと

struct Iterator{
    auto operator!=(Iterator){
        return false;
    }
    auto operator++(){
    }
    auto operator*(){
        return nullptr;
    }
};

struct Foo{
    auto begin(){
        return Iterator();
    }
    auto end(){
        return Iterator();
    }
};

int main()
{
    Foo foo;
    for(auto item: foo){
    }
}

はい、これだけです。
Iteratorに既存のものを利用する場合はなんとbeginとendの関数を定義するだけでrange based forに対応できてしまうのです。
ただ、注意点もあります。
Iterator != Iteratorでループの終了条件を判定するので同じコンテナのIteratorを返すようにしないといけません。
もっと自由度を高くしたい場合は専用のイテレータを作った方が良いでしょう。

後、const版のbeginとendを作っておいた方が後々幸せになれます。

カスタムイテレータ

イテレータの方もそこまで難しいことはなく、range based forの判定に必要な関数を定義するだけです。
operator!=で現在のイテレータとend()のイテレータを比較します。現在のイテレータとend()のイテレータが一致した場合にループ処理を終了させます。
operator++で現在のイテレータを次のイテレータに変換します。
operator*でイテレータからforで使う値を取り出します。

auto item = *iterator;

と同じですね。

展開されるコード

上記クラスがどう処理されるのかも記述しておきます。

for (auto __begin = foo.begin(), __end = foo.end(); __begin != __end; ++__begin ) {
  auto item = *__begin;
  // range based forの中身
}

begin(), end(), operator!=, operator++, operator*が何故必要なのか一目でわかりますね。

参考

Range-Based For Loop Wording (Without Concepts)