boost::adaptors::transformed
久々のまともな記事。
boost::adaptors::transformed は、range の値に対して操作を行ないます。
#include <iostream> #include <boost/range/adaptor/transformed.hpp> #include <boost/range/algorithm/for_each.hpp> int to_double(int n){ return n + n; } int main(){ namespace adaptors = boost::adaptors; int array[] = {0, 1, 2, 3, 4}; boost::for_each(array|adaptors::transformed(to_double), [](int n){ std::cout << n << std::endl; }); // こっちの記述でも OK adaptors::transform(array, to_double); return 0; }
[出力]
02468
行ってる処理自体はそんなに難しくありません。
adaptors::transformed に対して関数を渡し、捜査する際にその関数を通して値を返します。
// こうやって処理しているのと同じ for( int i = 0 ; i < 5 ; i++ ){ std::cout << to_double(array[i]); }
また、関数の戻り値型は、range の値以外も返すことが出来ます
// string 型に変換して返す std::string to_string(int n){ return boost::lexical_cast<std::string>(n); } int array[] = {1, 10, 100, 1000, 10000}; boost::for_each(array|adaptors::transformed(to_string), [](const std::string& str){ std::cout << str.length(); });
[出力]
12345
こんな感じで、string 型にして返すことも可能です。
さて、割と使い勝手がよさそうな、adaptors::transformed ですが、これにはいくつか落とし穴があります。
1.ラムダを渡すことが出来ない
C++0x で導入される予定のラムダ式と boost::lambda は、渡すことが出来ません。
int array[] = {0, 1, 2, 3, 4}; array|adaptors::transformed([](int n)->int{ return n + n; }); array|adaptors::transformed(boost::lambda::_1 + boost::lambda::_1);
これは、関数の戻り値型が特定できないのが原因です。
また、戻り値型かよ。
ちなみに、pstade::oven::transformed であれば、boost::lambda が使用できます。
// boost とは違うのだよ boost とは(キリッ
array|oven::transformed(boost::lambda::_1 + boost::lambda::_1);
[追記]
C++0x のラムダ式も oven::regular を使用すれば出来るみたいです。
詳細はこちらを参照してください。
2.操作を行うタイミングと実際の値の変化
例えば、
int to_double(int n){ return n + n; } int array[] = {0, 1, 2, 3, 4}; array|adaptors::transformed(to_double);
を行っても、array は、to_double で操作されません。
なぜなら、array|adaptors::transformed は、range を返してるだけであって、走査を行わないからです。
(操作と走査がややこしい…。)
では、いつ操作されるのかというとこのタイミングです。
// 戻り値型がややこしいので auto で取得 auto range = array|adaptors::transformed(to_double); assert( range[0] == to_double(array[0]) ); // range から値を取得する時に呼ばれる!
この様に値を取得するタイミングで、adaptors::transformed に渡した関数を介して値の取得を行います。
また、上記のような処理では、array の中身は変化しません。
array の値を変更する場合は、値のコピーを行います。
boost::copy(array|adaptors::transformed(to_double), array);
3.関数オブジェクトを渡す
関数オブジェクトを渡す場合は、戻り値型を定義しておかなければなりません。
struct plus{ plus(int rhs) : value(rhs){} typedef int result_type; // また、戻り値型か・・・ result_type operator ()(int& n) const{ return n; } private: int value; }; int array[] = {0, 1, 2, 3, 4}; boost::for_each(array|adaptors::transformed(plus(10)), [](int n){ std::cout << n << std::endl; });
この様に、クラスで戻り値型を定義しておく必要があります。
operator ()() が template 関数で、戻り値が、template の引数型に依存する場合は知りません。