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

参照への参照問題という名のbindの制約

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) ); // こうやることは可能?

といった前回の続きです。
さて、コンパイルすればすぐにわかるのですが、駄目です。コンパイルが通りません。いくつかエラーが出てしまいます。

さて、これは何が問題になっているのでしょうか?以下、ややこしい話をするので結論を先に書いておくと、「bindする関数オブジェクトに参照型を引数に取る物はセットできません」ということになります。以下、その説明です。気になる人はヘッダを横に置きながらどうぞ。

私はVisual Studio 2003でコンパイルしているのですが、出てくるエラーのうち、

error C2529: '_Left' : 参照への参照は無効です。

というエラーに注目してみました。これはbind2ndが作成してくれる関数オブジェクトbinder2ndのoperator()のオーバーロードの定義なのですが、ソースを辿ると次のような記述がされています。

template
    class binder2nd
        : public unary_function
    {    // functor adapter _Func(left, stored)

...

    result_type operator()(const argument_type& _Left) const // (*C)
        {    // apply functor to operands
        return (op(_Left, value));
        }

(*C)でconst argument_type& _Leftというものがありますが、ヘッダを読むとbinder2nd::argument_typeというのは、実はbinary_function::first_argument_typeのtypedefであるということが分かります。
今、SIncrementer2_2の定義を見直すと、binary_function::first_argument_typeにあたるものはテンプレートの第1引数の型ですから*1、int&です。

  • binary_function::first_argument_typeはint&である
  • binder2nd::argument_typeは実はbinary_function::first_argument_typeである
  • よってbinder2nd::argument_typeはint&である

となります。では、今エラーが出ている(*C)の const argument_type& は、どういう型になるでしょうか?そう、int&への参照になりますね。
そうです、これがコンパイルエラーの元凶なのです。参照への参照は作れない、というC++の制約に引っかかってしまうのです。

以上の問題について、「Radium Software Development」 の Reference to Reference という記事で記述がされていましたが、具体的にどういう引っかかり方をするのかが気になったので調べてみました。
http://www.radiumsoftware.com/0304.html#030417
この記事曰く、「回避策としてはboost::bind2ndを使う事」、らしいですね。
この記事を読んでますますboostの勉強をしたくなりましたよ。C++の次期標準*2にも含まれるらしいですし、勉強しておくべきライブラリの一つなのでしょうね。

ちなみに自分はどうしたかというと、この場合のbindする値を関数オブジェクトに持たせてunary_functionで機能を実現するという回避策を取りました。こんな感じです。

struct SIncrementer3
{
    int incValue;
    SIncrementer3( int incValue_ ) {
        incValue = incValue_;
    }
    void operator()( int& rValue) {
        rValue += incValue;
    }
};

...

for_each( values.begin(), values.end(), SIncrementer3( 3 ) );

この例においては、わざわざbindしないでこれで全然事足りましたね。

というわけで、アルゴリズムに関するお話はひとまずこれでおしまいです。誰かの役に立てば嬉しいな、と思います。
眠いのでそろそろ寝ます。ぐぅ。

*1:これもヘッダをたどるとtypedefされてます

*2:いつなのかなあ