アルゴリズムのそれってこーいうことか(その3)

bindの使用制限…意外と使いにくい?

for_each( ..., SIncrementer1() );
のような書き方をしたときに、なぜコピーコンストラクタの回数が抑えられるのでしょうか?

という、前回の話の続きです。
ここはまたややこしい話なのですが、関数が構造体やクラスを引数に受け取る時にSIncrementer1()のような形で引数に渡すと、一時変数はそこに生成されず、通常のコンストラクタだけが走るのです*1前回の話の(*B)で「基本的に」、と書いたのはそういう意味です。この辺の情報をどこかで見た覚えがあるのですが、情報ソースを失念してしまいました…。ググルにしてもキーワードが…。
まあなんにしても

for_each( ..., SIncrementer1() );

みたいな書き方にしておくのが無難というものなのでしょう。もう一ついえることは、アルゴリズムにおいて関数オブジェクトは値渡しであり、必ずコピーが発生するものなので、なるべくコピーコストの低い実装にするがいいよ、ってことです。「C++ Coding Standards」の89番目のルールにもそんなことが書いてありましたね。この本ではこれをPimplイディオムによりうまく解決する為のヒントが載っていました。
C++ Coding Standards―101のルール、ガイドライン、ベストプラクティス (C++ in‐depth series)
今のところ6割くらい読んだのですが、目からうろこすぽーんな本でした。C++をこう使うとこんな楽ができますよ、ていう本です。そして他の言語使えばそんなトリッキーな事しなくてもいいよとか思われてるんだろうなと思いつつ選択肢が無いんだよ!!まあもちろん他の言語の勉強もしますが。rubyやらなかったら関数オブジェクトに興味も湧かなかっただろうし。より上位の言語を学ぶことで現在使用している言語においても良い影響はでるものです。

と、脱線したところで次こそアルゴリズムの話に入ります。
とりあえず、bindの話。具体的な使い方は世の中に転がっているので探して頂くということにして、やはり私がはまったところについてのメモです。

  • bind1st,bind2ndとか、面白げな関数オブジェクトラッパーが存在するのですが、ラップするためには元の関数は binary_functionから継承している必要があります
// これはbindには渡せない
struct SIncrementer2_1
{
    int operator()( int value, int incValue ) {
        return value + incValue;
    }
};

てな関数オブジェクトを作ったとしても、

    bind2nd( SIncrementer2_1(), 3);

みたいにつかおうとしたらコンパイラからもりもり文句を言われます。
下みたいに書きます。

// これでないと駄目っぽい
struct SIncrementer2_3 : public binary_function< int, int, int >
{
    result_type operator()( first_argument_type value, second_argument_type incValue ) const {
        return value + incValue;
    }
};

result_type, first_argument_type, second_argument_typeってのは、binary_functionが定義されているヘッダを見れば分かりますが、単なるテンプレート引数のtypedefです。名前が長いけれど忘れたらヘッダを見に行けばOKです。
で、上のような感じで関数オブジェクトを作ったとします。この関数オブジェクトの仕事は、value, incValueを受け取ってその和を返すというものです。これを例えばintのリストと共にtransformに渡してやる事で、intリストの内容を指定した値だけ増やすという作業が可能になります。具体的な使い方は下のとおりです。

list values(3); // たぶん0で初期化されているはず
transform( values.begin(), values.end(), values.begin(), bind2nd( SIncrementer2_3(), 4 ) );
// ここで全ての値が4になっているはず

さて、ここで気になるのは、「なぜfor_eachじゃなくてわざわざコピーの発生するtransformを使ってるの?」ということになります。
for_each( values.begin(), values.end(), bind2nd( SIncrementer2_3(), 4 ) );みたいな書き方は出来ないのでしょうか?
ちなみにintの参照型とintを受け取って、参照型の値を増加する関数オブジェクトは下のように作れます。

struct SIncrementer2_2 : public binary_function< int&, int, void >
{
    result_type operator()( first_argument_type rValue, second_argument_type incValue ) const {
        rValue += incValue;
    }
};

...
 
for_each( ..., bind2nd( SIncrementer2_2(), 4) ); // こうやることは可能?

さて上記のようなfor_eachは可能でしょうか?気になった人は試してみましょう。
(続く)

*1:もちろんSIncrementer1に引数付きコンストラクタがある場合Sincrementer1( 100 ) みたいにすると、引数付きのコンストラクタが走ります。