[c++] for_each と mem_fun の関係をメモ書き

c++ で lambda 式を使うと for_each を使うのが楽になる…のですが、一応、以前の書き方をメモ。

#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

static void f1( string s ) {
	cout << s << endl;
}

int main( void )
{
	vector<string> lines;
	lines.push_back("masuda");
	lines.push_back("tomoaki");
	lines.push_back("alice");

	// 普通にforを使う
	for( auto it=lines.begin(); it!=lines.end(); ++it ) {
		cout << (*it) << endl;
	}

	// for_each と lambda を使う
	for_each( lines.begin(), lines.end(),
		[](string s){ cout << s << endl; });

	// for_each と static 関数を使う
	for_each( lines.begin(), lines.end(), f1 );

	// for_each と auto 関数を使う
	auto f2 = [](string s){ cout << s << endl; };
	for_each( lines.begin(), lines.end(), f2 );

	return 0;
}

ノーマルに for 文を使って書くと一時変数が必要になる。auto が無かった時代には「vector::iterator it = lines.begin()」と書かないと駄目だったのですが、このあたりは auto を使えば十分。この長い書き方が嫌で typedef したりするのですが、今は不要です。

	// 普通にforを使う
	for( auto it=lines.begin(); it!=lines.end(); ++it ) {
		cout << (*it) << endl;
	}

for_each と lambda 式を使うとこんな感じ。iterator がそのままラムダ式の引数に入るのでそれを使えばOKです。
このあたりが LINQ っぽく書けるってことなのですが、このままではほど遠いかなと。まあ、慣れかもしれませんが、イテレーターを lambda 式の引数として受けないといけないのがいまいちです。

	// for_each と lambda を使う
	for_each( lines.begin(), lines.end(),
		[](string s){ cout << s << endl; });

lambda 式を auto で受けて関数ポインタとして使う方法です。lambda 式部分が長い場合や、動的に切り替えたい場合に使える…と思うのですがよくわからず。
本来は、この auto を関数の外側に出したいのですが、それを真面目に出すと…

	// for_each と auto 関数を使う
	auto f2 = [](string s){ cout << s << endl; };
	for_each( lines.begin(), lines.end(), f2 );

下記のように、処理関数を外側に出します。これだと普通ですね。というか、これが最初の for_each の使い方です。

static void f1( string s ) {
	cout << s << endl;
}

...
	// for_each と static 関数を使う
	for_each( lines.begin(), lines.end(), f1 );

■クラス内で for_each を使う

先の例では、main 関数内で使っていたのですが、今度はクラスのメソッド内で使ってみます。
使い方は、まあ、main 関数と一緒です。内部的に vector でコレクションを持っていて disp_for メソッドなり、一括で表示させる関数を想定しています。

class Alice
{
public:
	vector<string> lines;

	Alice() {
		lines.push_back("masuda");
		lines.push_back("tomoaki");
		lines.push_back("alice");
	}

	void disp_for()
	{
		// 普通のfor文
		for( auto it=lines.begin(); it!=lines.end(); ++it ) {
			cout << (*it) << endl;
		}
	}
	void disp_for_lambda() {
		// lambda 式を使う
		for_each( lines.begin(), lines.end(),
			[](string s){ cout << s << endl; });
	}
	void disp_for_auto() {
		// auto に代入して使う1
		auto f2 = [](string s){ cout << s << endl; };
		for_each( lines.begin(), lines.end(), f2 );
	}

	void f1(string s)
	{
		cout << s << endl;
	}
	void disp_for_inner_func()
	{
		// 出来そうで、できない???
		// for_each( lines.begin(), lines.end(), &Alice::f1 );
		// 素直に lambda 式から呼び出す
		for_each( lines.begin(), lines.end(),
			[this](string s){ this->f1(s); });
	}
};

main 関数とちょっと違うのは、以下の disp_for_inner_func メソッドのところです。
書き方としては、for_each で Alice クラスの内部関数 f1 を使いたいところなのですが…これは出来ません。
関数ポインタ「&Alice::f1」を使って、lambda 式のようにイテレーターを引数にして、と思って使ってみると、どうやってもコンパイルが通りません。

	void f1(string s)
	{
		cout << s << endl;
	}
	void disp_for_inner_func()
	{
		// 出来そうで、できない???
		for_each( lines.begin(), lines.end(), &Alice::f1 );
		// 素直に lambda 式から呼び出す
		for_each( lines.begin(), lines.end(),
			[this](string s){ this->f1(s); });
	}

VC++ でコンパイルと以下のようなエラーになります。

C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\algorithm(22) : error C
2064: 1 引数を取り込む関数には評価されません。
        C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\algorithm(32) :
 コンパイルされたクラスの テンプレート のインスタンス化 '_Fn1 std::_For_each<std
::basic_string<_Elem,_Traits,_Ax>*,_Fn1>(_InIt,_InIt,_Fn1)' の参照を確認してくだ
さい
        with
        [
            _Fn1=void (__thiscall Alice::* )(std::string),
            _Elem=char,
            _Traits=std::char_traits<char>,
            _Ax=std::allocator<char>,
            _InIt=std::basic_string<char,std::char_traits<char>,std::allocator<c
har>> *
        ]
        alice029.cpp(66) : コンパイルされたクラスの テンプレート のインスタンス
化 '_Fn1 std::for_each<std::_Vector_iterator<_Myvec>,void(__thiscall Alice::* )(
std::string)>(_InIt,_InIt,_Fn1)' の参照を確認してください
        with
        [
            _Fn1=void (__thiscall Alice::* )(std::string),
            _Myvec=std::_Vector_val<std::string,std::allocator<std::string>>,
            _InIt=std::_Vector_iterator<std::_Vector_val<std::string,std::alloca
tor<std::string>>>
        ]

実は、これは for_each の使い方が間違っているので、コンパイルエラーになるのです。

■メンバ関数は mem_func を使う。ただし要素のメンバ関数に限る

for_each で設定するループ関数は、実は「要素」に対するメソッドになります。lambda 式を使うと分かりづらいのですが、先の for_each では、vector の string に対して関数が適用される、という意味になります。
なので、メンバ関数を for_each に指定する場合は、そのメンバ関数を持つ要素クラスをつくらないと駄目なわけですよ。なるほど。

class Point {
public:
	int x, y, z;
public:
	Point(int x, int y, int z ) {
		this->x = x;
		this->y = y;
		this->z = z;
	}
	void disp() {
		cout << x << "," << y << "," << z << endl;
	}
};

int main( void )
{
	// mem_fun はこう使う
	vector<Point*> ps;
	ps.push_back( new Point(1,1,1));
	ps.push_back( new Point(2,2,2));
	ps.push_back( new Point(3,3,3));
	for_each( ps.begin(), ps.end(), mem_fun(&Point::disp));
}

こんな風に、Point クラスを作っておいて、disp メソッドがあるという場合を想定します。
vector コレクションに対して、for_each を適用すると、この disp メソッドを適用する、ということになるのです。
で、メソッド関数の場合にはそのまま適用できないので、上記のように mem_fun という補助関数を使います。内部的には、vecotr::iterator に対して disp メソッドを呼び出すという、関数ポインタを使うという作りになっています。

確かに、for_each だけでなく、remove_if や find_if のような比較関数を設定する場合、要素に対して comp 関数がある訳で、納得できる仕様ですよね。

ちなみに、find_if に比較関数 comp を追加して表示させるとこんな風になる。

class Point {
public:
	int x, y, z;
public:
	Point(int x, int y, int z ) {
		this->x = x;
		this->y = y;
		this->z = z;
	}
	void disp() {
		cout << x << "," << y << "," << z << endl;
	}
	bool comp( Point *pt ) {
		if ( this->x == pt->x &&
		     this->y == pt->y &&
		     this->z == pt->z ) {
			return true;
		} else {
			return false;
		}
	}
};

int main( void )
{
	Point *p1 = new Point(2,2,2);
	auto it = find_if( ps.begin(), ps.end(),
		bind2nd(mem_fun(&Point::comp),p1));
	if ( it != ps.end() ) {
		cout << "found." << endl;
	} else {
		cout << "no found." << endl;
	}
	return 0;
}

比較する Point ポインタを渡すために、bind2nd 関数を使うところが「アレ」ですが、まぁ、できます。
うーん、C++ template パズルを作りたい場合はこれでもいいのですが、実運用として(特に可読性としては)はこれはいまいちかなぁと常々思っています。

この comp という比較関数ですが、Point クラスに属さないと駄目なところが欠点ですよね。vector の場合はよいのですが、さて、vectorとか、vectorの場合はどうすれば良いのか?ってことです。比較関数として equal_to() とか使えばいいんですかね?

	auto b = find_if( lines.begin(), lines.end(),
		bind2nd( equal_to<string>(), "tomoaki" ));
	if ( b != lines.end() ) {
		cout << "found." << endl;
	} else {
		cout << "no found." << endl;
	}

まあ、慣れるとそれでもいいのですが、lambda 式があったり LINQ があったりする時代なので、ちょっとパズルは嫌だなぁと。
今だったら、下記のように lambda 式を使います。

	// lambda を使う
	auto b2 = find_if( lines.begin(), lines.end(),
		[](string s)->bool{ return s == "tomoaki"; });
	if ( b2 != lines.end() ) {
		cout << "found." << endl;
	} else {
		cout << "no found." << endl;
	}

このほうが可読性が高いです、と私は思います。

■プリミティブなクラスに比較関数をつけるのか?

で、話を戻すと、

	void disp_for_inner_func()
	{
		// 出来そうで、できない???
		// for_each( lines.begin(), lines.end(), &Alice::f1 );
		// 素直に lambda 式から呼び出す
		for_each( lines.begin(), lines.end(),
			[this](string s){ this->f1(s); });
	}

のところで、内部関数を渡せるほうが読み方としては私にはなんとなく自然な訳です。remove_if や find_if には equal_to のような関数が用意されていますが、カスタム表示のような disp 関数の場合は無理ですよね。
逃れる方法としては、ラムダ式からメンバ関数を呼び出すので、これで十分用途は足りるのですがなんかダサい。。。

class Point {
public:
	int x, y, z;
public:
	Point(int x, int y, int z ) {
		this->x = x;
		this->y = y;
		this->z = z;
	}
	void disp() {
		cout << x << "," << y << "," << z << endl;
	}
	bool comp( Point *pt ) {
		if ( this->x == pt->x &&
		     this->y == pt->y &&
		     this->z == pt->z ) {
			return true;
		} else {
			return false;
		}
	}
};

あと、発想として Point クラスに比較関数などなどが付け加わるのもいまいちなのです。Point クラスはデータクラスとして必要最低限にとどめておきたいし、のちのちに拡張させないようにしたい。C++ 的には include 先が変更されるのは避けたいわけで、そうなると「将来追加されるかもしれない」comp 関数などを追加しておくのは「到底無理」な訳です。
そうなると、comp 関数のほうも使うときに適宜用意するのが適当で、

Alice {
	vector<Point*> ps;
public:
	bool comp( Point *p1, Point *p2 ) {
		if ( p1->x == p2->x &&
             p1->x == p2->y &&
             p1->x == p2->z ) {
			return true;
		} else {
			return false;
		}
	}
	bool find( Point *p ) {
		return for_each( ps.begin(), ps.end(),
			&Alice::comp, p );
	}
};

な風に、2つの引数を持つ comp メソッドを for_each から直接呼び出せたらよいなぁ、と。comp メソッドの中身はここでは x,y,z の全てを比較していますが、workarea などを含めると独自に comp を作ったほうが良い場合が多いのです。高速化のために内部的には id を比較するだけとか。

あとは、LINQ 風に

bool find( Point *p ) {
	return ps.from( it ).where(it == *p );
}

あるいは

bool find( Point *p ) {
	auto result = ps.from(it).where(it==*p);
	return result != ps.end();
}

としてしまうとか。まあ、こっちのは話はまた別の機会に。

カテゴリー: C++ パーマリンク

[c++] for_each と mem_fun の関係をメモ書き への2件のフィードバック

  1. a のコメント:

    ×lamda

    ○lambda

    • masuda のコメント:

      うわッ!!! ラムダ式に慣れてないのがバレバレやん…つーことで、s/lamda/lambda/g することに。

コメントは停止中です。