はじめに
みなさんこんにちは。
通常イテレータといえば、まず思いつくのはコンテナの要素を順々にたぐるアレだと思いますが、STLにはそうでないイテレータもあります。それはイテレータによってアルゴリズムを汎用的に書けるようになったことで可能になった応用的なイテレータですが、非常に有用な反面とっつきにくいイメージがあります。そこで、この記事ではSTLに存在する特殊なイテレータをSTLに存在する通常のアルゴリズムを使って紹介していきたいと思います。
reference:
http://en.cppreference.com/w/cpp/iterator
http://en.cppreference.com/w/cpp/algorithm
通常のイテレータ
イテレータがゲシュタルト崩壊したときに見てください。
std::vector< int > vec { 1, 2, 3, 4, 5 };
typename std::vector< int >::iterator itr = begin(vec);
// output
*itr = 5; // assign value
++itr; // points to the next element.
// input
int n = *itr; // 2
ostream_iterator, ostreambuf_iterator
category: outputiterator
ostream_iteratorは出力ストリームの現在位置を指すイテレータです。イテレータに値を設定することはストリームに値を流すことと同義と言えます。また、出力演算子が定義されている型であれば何でも扱うことができます。
streambuf_iteratorはストリームに関連付けられたバッファを指すイテレータです。ストリームイテレータと違い、バッファに直接値を突っ込むため効率はいいですが、当然ながらバッファと同じ型しか与えることができません。
std::ostream_iterator< int > itr(std::cout);
*itr = 123; // std::cout << 123;
++itr; // nop
std::vector< int > vec{4, 5, 6};
std::copy(begin(vec), end(vec), itr); // outputs 456
std::ostreambuf_iterator< char > bufitr(std::cout.rdbuf()); // typeof the cout's buffer is char.
*bufitr = 'h'; // std::cout.rdbuf()->sputc('h');
std::string str = "ello world";
std::copy(begin(str), end(str), bufitr); // outputs ello world
istream_iterator, istreambuf_iterator
category: inputiterator
入力ストリームの現在位置を指すイテレータです。イテレータから値を読み出すことはストリームから値を読むことと同義です。また、イテレータを進めることはストリームの現在位置を進めることと同義です。
コンストラクタに何も与えないと、終端を意味するイテレータを作成できます。
std::istringstream iss("1 2 3 4 5 6");
std::istream_iterator< int > itr(iss);
int n = *itr; // 1
std::cout << n << std::endl; // outputs 1
++itr; // points to the next element
int m = std::accumulate(itr, {}, 0); // 0 + 2 + 3 + 4 + 5+ 6
std::cout << m << std::endl; // outputs 20
std::istream_iterator< int > itr2(iss); // points end
int l = std::accumulate(itr2, {}, 0); // 0
std::cout << l << std::endl; // outputs 0
reverse_iterator
言わずと知れたリバースイテレータなので説明不要かと思います。
C++14からはヘルパ関数を使うことができます。
make_reverse_iterator(C++14)
move_iterator
category: depends on the argument
読み取り専用のイテレータです。このイテレータから読み込まれる値はムーブされて渡されます。
ヘルパ関数のmake_move_iteratorが用意されています。
異なるコンテナ間で内容をムーブするのに便利ですね。
std::vector< std::string > vec {"hello", "beautiful", "world"};
std::list< std::string > list(
std::make_move_iterator(begin(vec)),
std::make_move_iterator(end(vec)));
// output nothing
std::copy(begin(vec), end(vec),
std::ostream_iterator< std::string >(std::cout, " "));
// outputs hello beautiful world
std::copy(begin(list), end(list),
std::ostream_iterator< std::string >(std::cout, " "));
back_insert_iterator
category: outputiterator
通常であればoutputiteratorが必要とされるところでも、出力先のコンテナには予め領域が確保されてなければなりません。容量が足りずに出力先のコンテナのend()の指す場所に突入した時点でバッファオーバーフローが起こってしまうからです。
std::vector< int > vec1 {1, 2, 3};
std::vector< int > vec2;
// vec2.resize(vec1.size());
std::copy(begin(vec1), end(vec1), begin(vec2)); // oops!
このイテレータは割り当てられた値をpush_backで追加してくれるので、そのような心配はありません。
ただし、コンテナがpush_back関数を持っている必要があります。
また、便利なヘルパ関数back_inserterを使うことができます。
std::vector< int > vec1 {1, 2, 3, 4, 5};
std::vector< int > vec2;
std::copy(begin(vec1), end(vec1), std::back_inserter(vec2));
// outputs 12345
std::copy(begin(vec2), end(vec2), std::ostream_iterator< int >(std::cout));
front_insert_iterator
category: outputiterator
back_insert_iteratorと基本的に同じものですが、back_insert_iteratorがpush_backで要素を追加するのに対して、push_frontを使って要素を追加してくれます。よって、コンテナはpush_frontを持っている必要があります。
back_insert_iterator同様にヘルパ関数が用意されています。front_inserter
std::list < int > list1 {1, 2, 3, 4, 5};
std::list < int > list2;
std::copy(begin(list1), end(list1), std::front_inserter(list2));
// outputs 54321
std::copy(begin(list2), end(list2), std::ostream_iterator< int >(std::cout));
insert_iterator
category: outputiterator
お察しの通り、insertを使って要素を追加してくれるイテレータです。やはりお察しの通り、コンテナはinsertを持っている必要があります。
ヘルパ関数はinserterを使うことができます。
std::list < int > list1 {3, 4, 5, 6};
std::list < int > list2 {1, 2, 7, 8};
auto itr = begin(list2);
std::advance(itr, 2);
std::copy(begin(list1), end(list1), std::inserter(list2, itr));
// outputs 12345678
std::copy(begin(list2), end(list2), std::ostream_iterator< int >(std::cout));
おわりに
いかがだったでしょうか。紹介したのは嫌われやすいイテレータたちですが、アルゴリズムヘッダに入っているアルゴリズムをフル活用できるようになるのでぜひ使ってあげてくださいね。
また使用する際は、カテゴリに注意してください。彼らは基本的に(カテゴリとして)低機能なイテレータですから、自分より高機能なイテレータを求めているアルゴリズムに突っ込むことはできません。
間違いなど気付いた点がございましたらコメントの方へよろしくお願いします。