[C++] イテレータ with ループカウンタ

 イテレータで反復処理を行うとき、ループカウンタとりたい時ってあるじゃないですか。vectorならイテレータ同士の引き算で位置が求まりますが、なんかアレな気がしないですか。しないですか。そうですか。でもランダムアクセスイテレータだったらdistance使わなきゃないじゃないですか。いいですか。そうですか。もういっその事別にループカウンタ用の変数置けばいいじゃないですか。でもこれってなんか負けた感じがするんですよね…
 ということで今回は反復しつつカウンタの値も取るコードを書いてみました。

とりあえず

int i = 0;
for(std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it)
{
    hoge(i);
    i++;
}

で問題なさそうならこれでいいと思います!

両方とりたいアナタに

 以下にコードを貼ります。

#include <iostream>
#include <vector>
/* -- ここから -- */
template<typename T, typename C = int>
class Element
{
template<typename S>
friend class Iterator;
private:
inline void set(const C i, T &v)
{
index = i;
value = v;
}
public:
C index;
T &value;
inline Element(const C idx, T &val) : index(idx), value(val) { }
};
template<typename T>
class Iterator
{
typedef typename T::iterator iter_type;
typedef Element<typename T::value_type> elem_type;
private:
int index;
iter_type iterator;
elem_type elem;
public:
inline Iterator(iter_type it) : index(0), iterator(it), elem(0, *it) { }
inline bool operator!=(const Iterator<T> &it) const { return iterator != it.iterator; }
inline elem_type &operator*() { return elem; }
inline elem_type *operator->() { return &(operator*());}
inline const Iterator &operator++()
{
elem.set(++index, *(++iterator));
return *this;
}
};
template<typename T>
class WithIndex
{
public:
typedef Iterator<T> iterator;
private:
T &iterable;
public:
inline WithIndex(T &iterable) : iterable(iterable) { }
inline Iterator<T> begin() const { return Iterator<T>(iterable.begin()); }
inline Iterator<T> end() const { return Iterator<T>(iterable.end()); }
};
template<typename T>
inline WithIndex<T> withIndex(T &iterable) { return WithIndex<T>(iterable); }
/* -- ここまで -- */
int main(int argc, const char * argv[])
{
std::vector<int> iv = { 2, 4, 6, 8, 10 };
for(auto &e : withIndex(iv)) // インデックスも取る!
{
std::cout << e.index << ": " << e.value << std::endl;
}
return 0;
}
view raw withIndex.cpp hosted with ❤ by GitHub

 こんな感じ。main関数内はC++11な機能を使ってますが、WithIndexクラス自体はC++03でもコンパイルできます(できるはずです)。range-based forとの相性がよさそうです。普通のforを使う場合は、

typedef WithIndex<std::vector<int> > VectorWithIndex;
VectorWithIndex vi = withIndex(iv);
for(VectorWithIndex::iterator it = vi.begin(); it != vi.end(); ++it)
{
    std::cout << it->index << ": " << it->value << std::endl;
}

 こんな感じで。うーん、autoがないとちょっと使いづらい。いや、かなり使いづらい。

ちょっとした説明

 コード自体はScalaのzipWithIndexを真似てます。こういうシチュエーションってよくあると思うので、ググればきっとたくさんヒットすることでしょう。
 イテレータのindexにループカウンタの値が、valueに実際のイテレータの値が代入されます。range-based forならアロー演算子が使われないのでシンプルなコードになるんですが、C++11より前のC++ではイテレータに入っている値を取り出す際にアロー演算子を使うことが多いかと思います。アロー演算子はポインタを返さなければならないため、一度Elementクラスを変数として書き出しています。ちょっとかっこ悪いしなんだかパフォーマンス落ちてそう。
 range-based forがとても便利です。beginとendがそれぞれ先頭のイテレータ、末尾のイテレータを返す実装になっていれば利用可能です。autoも使えば反復処理がこんなに簡単に!

0 件のコメント :

コメントを投稿