Boost.Rangeが流行っている気がするので解説します。それぞれの細かい機能についてはリファレンスを見てね。この記事はBoost.Rangeが分からない人、特に全く知らない向けの記事になっているため、boostが1ミリも分からなくても読める記事になっているつもりです。なお、私がこの記事を書く上で使用したBoostのバージョンは1.60.0です。リファレンスは以下です。URLをちょっといじれば好きなバージョンに飛べると思います。
http://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/
なお、標準ライブラリのアルゴリズムヘッダも参照のこと。
http://en.cppreference.com/w/cpp/algorithm
#Boost.Rangeとは
イテレータ対(iterator pair)というイディオムを応用発展させたboostのライブラリ。イテレータ対というのは、標準ライブラリでも非常によく使われるイディオムです。
iterator pairについてはc++ more idiomsに詳しいです。
https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Iterator_Pair
std::fill(begin(vec), end(vec), // これとか
0xff);
std::copy(begin(vec), end(vec), // これとか
std::ostream_iterator<int>(std::cout));
std::list<int> list(begin(vec), end(vec)); // これのこと
コンテナそのものを渡さずに、beginとendというイテレータの対を使ってコンテナではなく"範囲"をやり取りすることで汎用性を上げるイディオムです。でも正直、面倒くさい。
#include <iterator> // 必須
using std::begin; using std::end; // ADLによって呼び出されなければならない為
begin(container); end(container); // containerが2回も登場する
ひどい話ですが、上のように書かなければなりません。STLのアルゴリズム関数は汎用性に優れ、高速で、問題の解決能力も高いですが、ボコボコに罵られることもあります。iterators must goは有名ですね。iterators must goは完全に和訳するのは難しいですが流し読みするのはそう難しくないのでぜひ一読することをおすすめします。
そこでRangeという"範囲"という概念を表すクラスが登場するというのはそんなに革新的な話ではないと思います。beginとendのイテレータのペアを保持するクラスに、emptyやbegin, endなどのメンバ関数を持たせ、コンテナよりも緩いconceptの元で使える、そんな範囲を扱うBoost.Rangeの機能、魅力、そして流行り?のパイプライン記法についても解説したいと思います。
#Boost.Range.Algorithms
Rangeを手に入れてまず思いつくのは、上の面倒くさいアルゴリズム関数の引数をRangeにしてしまうことですね。これだけでも、だいぶ楽ちんになります。Boost.Algorithmとごっちゃにしないように気を付けてください。
機能ごとにヘッダが用意されています。使いたいものだけインクルードしましょう。
http://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/headers/algorithm.html
#include <iterator>
#include <vector>
#include <boost/range/algorithm/copy.hpp>
int main()
{
std::vector<int> vec { /* initialize */ };
boost::copy(vec, std::ostream_iterator<int>(std::cout));
}
はい。言いたいことは分かります。ヘッダ名が長い。コンパイル時間も長い気がする。ていうかこのくらい標準でできなかったことが疑問。いえいえ、まだ触りですから、まだまだ序の口です。これでも結構楽になったと思いますけどね。さあ、次行きましょう。
#Boost.Range.Utilities
Rangeが範囲を表すなら、何もbeginとendのペアじゃなくてもいいよね。begin+2 ~ end-8 の範囲だって範囲じゃん。そんなあなたの乙女心をわしづかみにするのがこのBoost.Range.Utilitiesです。
Utilitiesには2つのクラス、すなわちiterator_range
とsub_range
の2つが用意されています。
iterator_range
はイテレータの型を指定して、そのイテレータの範囲を示すクラスです。sub_range
はコンテナの型を指定して、そのコンテナの一部の範囲を示すクラスです。便利の為にテンプレート引数の違うこの二つが用意されていますが、実際にはこれらは同じ実装になっています。
#include <boost/range/iterator_range.hpp>
#include <boost/range/sub_range.hpp>
#include <vector>
int main()
{
std::vector<int> vec { /* initialize */ };
boost::sub_range<std::vector<char>> sub(vec); // vecの最初から最後まで
boost::sub_range<std::vector<char>> sub2(begin(vec)+2, begin(vec)+10); // vecの2番目から9番目までを表す
boost::iterator_range<std::istream_iterator<int>> sub3(
std::istream_iterator<int>(std::cin),
std::istream_iterator<int>()); // イテレータ対が基になっているから、こういう応用も効く
}
#Boost.Range.Adaptors
これが本題でしょうか。かなり応用的な話になってしまいます。このライブラリはC++でパイプライン記法のものが使えるようになるライブラリで、かなり黒魔術の様相を呈した見た目ではありますがとっても実用的な形になっています。しかし出入力ストリームと同じように演算子のオーバーロードを悪用しています。
これもヘッダがそれぞれ用意されているので、自分が使うものをインクルードしましょう。
http://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/headers/adaptors.html
boost::irange
はstd::iota
のrange版のようなもので、指定した数値から数値までのrangeを作成してくれる関数です。
#include <vector>
#include <boost/range/irange.hpp>
#include <boost/range/adaptor/strided.hpp>
#include <boost/range/adaptor/transformed.hpp>
int main()
{
// 適当な数列を作ってみる。
boost::copy(
boost::irange(0, 100) // 0~99の要素のrangeを作成; 0, 1, 2, 3, ..., 99
| boost::adaptors::strided(2) // 要素を1つ飛ばしにする; 0, 2, 4, 6, ..., 98
| boost::adaptors::transformed([](int n) { return n*2; }), // 要素を全て2倍にする; 0, 4, 8, 12, ..., 196
std::ostream_iterator<int>(std::cout, " "));
// output: 0, 4, 8, 12, ..., 196
// 文字列を作ってみる
std::string str;
boost::copy(
boost::irange<char>('a', 'z'+1) // 'a'~'z'の要素のrangeを作成; abcd...z
| boost::adaptors::transformed([](char c) { return c - ('a' - 'A'); }) // 要素を全て大文字にする; ABCD...Z
| boost::adaptors::reversed, // 要素を逆順にするZYXW...A
std::back_inserter(str));
std::cout << str << std::endl; // output: ZYXW...A
}
ちなみに、adaptorは必ずパイプに使うものと関数として使うものとがあり、boost::adaptors::transform
に対するboost::adaptors::transformed
、boost::adaptors::stride
に対するboost::adaptors::strided
のように対応関係にあります。
##adaptorを自作してみよう
黒魔術に見えますが実はそんなに難しくないです。adaptorがどうやって出来ているのかを知ることでいざコンパイルエラーが出てしまった時にも対処がしやすいです。ということで、簡単なadaptorを自作してみましょう。ここではfor_eached
というものを作ってみます。
必ず対になるという慣習を守ることにします。と言っても、for_each
ならもう片っぽはあるんですけどね。
boost::for_each(range, [](value_type e) { std::cout << e; }); // 普通のfor_each
range | for_eached([](value_type e) { std::cout << e; }); // pipe用のfor_each
std::string str;
boost::copy(
boost::irange<char>('a', 'z'+1)
| for_eached([](char e) { std::cout << e; }) // abcd...zと標準出力に出力する
| boost::adaptors::transformed([](char c) { return c - ('a' - 'A'); })
| for_eached([](char e) { std::cout << e; }) // ABCD...Zと標準出力に出力する
| boost::adaptors::reversed,
std::back_inserter(str));
なんか微妙ですが、まあこんなものを作りたいと思うわけです。基本的には、タグディスパッチ(tag dispatching)という手法を使います。関数名はoperator|
で固定で、第一引数はRange&
またはconst Range&
でなくてはなりませんね。戻り値は第一引数と同じものを返します。なにやら出力ストリーム演算子とそっくりですね。全くこの手の悪習には困ります。
見ての通りfor_eached
はテンプレートを使わなきゃいけないので、テンプレート関数にしましょう。テンプレートが不要であればクラスを使って、コンストラクタで引数を受け取って直接タグディスパッチをかましてもいいですね。
とにかくここではテンプレート関数でfor_eached
を作成して、operator|
の第二引数に相当する型を返してやればいいわけです。
それでは、operator|
の第二引数に相当する型から作りましょう。for_eached_tag
という名前にしました。部分特殊化によってoperator|
のオーバーロードを正確に行うためだけのクラスなので、受け取った関数をラップするだけになっています。
そうしたら、operator|
を作ります。ここでrangeと関数が揃うので、for_each
を実行してあげます。
最後にoperator|
を呼び出すためのfor_eached_tag
を作るための関数、for_eached
を作ってあげれば完成です。
ね、簡単でしょう。
template<class UnaryFunction>
class for_eached_tag
{
UnaryFunction f;
public:
for_eached_tag(UnaryFunction f) noexcept : f(f) {}
template<class T>
auto operator()(T&& arg)
->decltype(f(std::forward<T>(arg)))
{ return f(std::forward<T>(arg)); }
template<class T>
auto operator()(T&& arg) const
->decltype(f(std::forward<T>(arg)))
{ return f(std::forward<T>(arg)); }
};
template<class Range, class UnaryFunction>
static inline Range& operator|(Range& r, for_eached_tag<UnaryFunction> func)
{ boost::for_each(r, func); return (r); }
template<class Range, class UnaryFunction>
static inline const Range& operator|(const Range& r, for_eached_tag<UnaryFunction> func)
{ boost::for_each(r, func); return (r); }
template<class UnaryFunction>
static inline for_eached_tag<UnaryFunction> for_eached(UnaryFunction func)
{ return for_eached_tag<UnaryFunction>(func); }
すでにqiitaに素晴らしい紹介があったので不要かとも思いましたが、コンテナへ変換してくれるアダプタも作ってみましょう。目指す形はこんな感じです。やりましたね、もうboost::copyを挟む必要はありません。
std::vector<int> vec = containerize(boost::irange(0, 100)); // 関数版
std::string str = boost::irange<char>('a', 'z'+1) | containerized; // pipe版
ここでは戻り値による関数のオーバーロードに似たイディオムを使います。
先ほどとは少し手順が変わっているのが面白いですね。最初に実際に変換を担当するクラスを作成し、それを使ってcontainerize
関数を作成します。そうしたらタグを作り、今回は引数は要らないのでタグをそのままoperator|
に渡してやればOKです。
template<class Range>
class containerizer
{
const Range& range;
public:
containerizer(const Range& r) noexcept : range(r) {}
template<class To>
operator To() const // これのおかげで戻り値によるオーバーロードもどきができる
{
using std::begin; using std::end;
return To(begin(range), end(range));
}
};
template<class Range>
static inline containerizer<Range> containerize(Range&& range)
{ return containerizer<Range>(std::forward<Range>(range)); }
struct containerized_tag {};
static constexpr containerized_tag containerized; // 今回はただのタグ
template<class Range>
static inline containerizer<Range> operator|(
Range&& range, containerized_tag)
{ return containerize(std::forward<Range>(range)); } // operator|でcontainerizeを実行する
#おわりに
結構大変な記事になってしまいましたが、これでもBoost.Rangeのごく一部しか紹介できていません。上のものをさらに楽に書くためのヘルパ関数などもあるので、気に入っていただけたらぜひBoostをダウンロードしてみて、使ってみてください。このライブラリのように、Boostにはヘッダオンリーで使えるライブラリがたくさんあります。この記事がBoostを使うきっかけになれば幸いです。
予定していたより長くなってしまいましたが、ここまで読んでいただけた方がいらっしゃったらありがとうございます。
また間違い、typo、私の理解の不足や説明の不足があればぜひ指摘をお願いいたします。
誤字が大量にありますね。
直したつもりですがまだまだありそう。