for_eachでメンバ関数を使う時の復習的自分メモ

in

for_eachでFuncにメンバ関数を指定するっていうお題はbins1st、mem_fun/mem_fun_refを使えってことになるのですがそれ以外にも落とし穴があります。なかなかまとめて説明しているものが見つからなかったので自分自身のおさらいを兼ねて...

class boo
{
public:
  :
  void foo(const std::vector<int>& v);
  void woo(const int x) const { cout << x << endl; };
}
void boo::foo(std::vector<int>& v)
{
  std::vector<int>::const_iterator itr=v.begin();
  while(itr!=v.end())
  {
    woo(*itr);
    ++itr;
  }
}

まず手持ちのシマリス本でfor_eachの定義を確認すると、

template<typename InIter,typename Func>
  Function for_each(InIter first,InIter last,Func f);
for_each関数テンプレートは、[first,last)の範囲内にある要素ごとにfを呼び出し、fの唯一の引数として要素を渡す。(C++ライブラリリファレンス/オライリーより)

ということになっています。ここで、Func fですが、大して考えもせず関数ポインタか関数オブジェクトが指定できるんだって思い込んでいたのですが、いざ調べだしてみると意外にも関数ポインタが使えるなんていう記述は見あたらず、例えば、MSDNなら

User-defined that is applied to each element in the range.

function objectとなっているし、元祖sgiでも、

UnaryFunction is a model of Unary Function.UnaryFunction does not apply any non-constant operation through its argument.
A Unary Function is a kind of function object: an object that is called as if it were an ordinary C++ function. A Unary Function is called with a single argument.

となっています。僕程度の英語力では解釈は難しいのですが、額面通り受け取ればFuncは関数オブジェクトとってことになります。(誰かハッキリしたこと教えてください!)

実際のところ誰でも書きたくなる

for_each(v.begin(),v.end(),&boo::woo);

to call pointer-to-member function in...(gcc-3.3)でエラーになります。やっぱりエラーメッセージでも”メンバ関数はダメ”だよと言ってきます。そこでmem_fun/mem_fun_refの登場ということになるわけです。mem_funの定義は

mem_fun関数テンプレートは、メンバ関数へのポインタを引数にとり、メンバ関数を呼び出す関数オブジェクトを返す

とあります。つまり、メンバ関数を呼び出す関数オブジェクトを生成してくれるわけです。であれば、

for_each(v.begin(),v.end(),mem_fun(&boo::woo))

でいいかというとそうでもないです。実際コンパイル(gcc-3.3)してみると、

no match for call
 void std::mem_func1_t::operator()(_Tp*,_Arg) const [with
 _Tp=boo, _Arg=int]

と言われます。つまりmem_funが生成する関数オブジェクトは、operator()(_Tp*,_Arg)として生成されるわけで、for_eachは、Funcにただ一つの引数しか(個々の要素)渡しませんから構文不一致を起こします。mem_funマジックといえども(メンバ)関数のポインタを貰っただけでは呼び出しを実行することはできませんからメンバ関数を持つオブジェクトのポインタ(=thisポインタ)も必要になるわけです。というわけでbind1stの登場となります。

二項関数への第一引数として常に同じ値を提供したい場合にbind1stを使用する

ここまで来ると半ばやけくそ気味ですが、まあそういうことです。

for_each(v.begin(),v.end(),bind1st(mem_fun(&boo::woo),this));

流れを確認すると、for_eachはbind1stを*InIterを引数として呼び出す、次にbind1stは第一引数をthisとして第二引数にはfor_eachからの*InIterをそのまま渡します。これでめでたくboo::woo(*InIter)が呼ばれるわけです。

ところでサンプルではコンテナをintとしていますが、実際のところコンテナの中はクラスオブジェクトであることが多いかと思います。引数を参照とするか値とするかについての良く知られたイディオムに『組み込み型は値、クラスオブジェクトは参照』というのがあって、この考えに従えば、例えばコンテナにclass_xを格納している場合のwoo_cxの定義は、

void woo_cx(const class_x& cx) 
{
  :
}

となります。さて、ここで先のfor_eachを適用するとエラーとなります。

error: forming reference to reference type ‘const class_x&’

もちろん僕もハマるまで知らなかったんですが、これが"reference to reference"問題です。要するにstd::bind*の実装では呼び出す際に強制的に引数に参照(&)を付加するためconst class_x&amp&となりコンパイルエラーとなるわけです。これに関しては

などを参考に熟考してみるのが楽しいです。肝心の回避策は結局boost::bind1stを使えってことになるようで、

for_each(vx.begin(),vx.end(),boost::bind1st(mem_fun(&boo::woo_cx),this));

こうなるともう、boost導入は必須って様相を呈してきます。しかし、”1行で書ける"とか"v.end()の評価が一度で済む"とかのメリットはあるものの、プログラムの読みやすさ、"何をしているのかを読む”を考えると、iterator廻してたっていいじゃないかとも思ったりもするわけです....

添付サイズ
foreach.cpp1.08 KB

この記事のトラックバックURL:

http://hippos-lab.com/blog/trackback/319

返信