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 の引数型に依存する場合は知りません。