何でもstd::for_eachで行こう


(後日追記)いやいやいや。
やあみんな。時は流れ、C++11の時代になったよ。今はもうラムダ式が使えるよ。C++11については、「C++ポケットリファレンス」などの本を見てもらうとして、以下本文に書いたようなことをやりたい場合、もう難しいことは一切考える必要はなくなって、以下のように1行で書けるよ。

auto& l = hillways;
for_each( l.begin(), l.end(), [](Hillway* h){ h->getStation()->North(); } );

ちなみにC++11だと以下のようにも書けてしまい、その方が文字数は少なくて済む。

for( auto h: hillways )
{
  h->getStation()->North();
}// h

ではfor_eachは何故使うのか?何がいいのか?それはたった一つ。「可読性」だ。それはつまり、if文による分岐処理や変数宣言、ネストループ、break文continue文など、ループの中でより自由に処理を書けてしまうfor文に比べて、for_eachで書いておけば、それを見た瞬間、ここでは各要素についてある決まった処理(基本的には1関数呼び出し)を繰り返し実行するだけですよというのが、見てすぐにわかるという点だ。あとは同じように可読性を高めるsortや、remove_ifなど、その他の兄弟アルゴリズムを使おうと思う正しい心が開けるという副次的効果か。
じゃな、チャオ!(チャオはciao, イタリア語でしかも、こんにちはとかさよなら、じゃあねの意味)

C++ ポケットリファレンス

C++ ポケットリファレンス

(以下は時代遅れの本文。)---------------
やあ子供たち、元気にしてたかい?今日はstd::for_each入門だよ。入門者の人たちなんかはまだまだなかなか使ってない人、使おうとしない人もたくさんいると思うのだけど、それはあまりにももったいない話だよ。コードが見違えるほどすっきりするし、おまけにテンプレートや関数オブジェクトについての理解も深まるよ。
Hillwayを通って行けば、Stationに辿りつく。ひとたびStationに辿りついてさえしまえば、そこからは東西南北、どこにでも行ける。そんな情景をC++のクラスとして表現してみたよ。

#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
#include <functional>
using namespace std;

class Station
{
public:
	Station( void ){}
	~Station( void ){}

	void North( void ){ cout<<"North"<<endl; }
	void East( void ){ cout<<"East"<<endl; }
	void West( void ){ cout<<"West"<<endl; }
	void South( void ){ cout<<"South"<<endl; }
};

class Hillway
{
public:
	Hillway( void ){ station_ = new Station; }
	~Hillway( void ){ delete station_; }
	Station* getStation( void ){ return station_; }
private:
	Station* station_;
}

今、Hillwayを12本ひき、その各々からStationにたどり着き、実際北へ行ってみた。そんな状況をプログラムにしてみた。

int main( void )
{
	vector< Hillway* > hillways;
	for( int i=0; i<12; ++i ){
		hillways.push_back( new Hillway );
	}// i
	for( int i=0; i<12; ++i ){
		hillways[i]->getStation()->North();
	}// i
	for( int i=0; i<12; ++i ){
		delete hillways[i];
	}// i
	return 0;
}

うーん、、もうちょっとスマートに書けないだろうか。できれば最初の生成以外はfor_eachを使いたい。ところが、hillway のvectorを持っているのであって、Stationのvector ではない。

  // Hillwayを通りStationに着き、北へ旅するときのコード
  hillway->getStation()->North(); 

なので for_each の述語はどうしよう。HillwayからNorthまではStationを経なくては、つまり関数呼び出しを2段階で行わなくてはならない。1段階呼び出ししかできないmem_funは使えそうにない。
いやいや、こう考えよう。
「hillwayの配列をfor_eachで回すためには、Hillwayのポインタを受け取り、それが指すStationのポインタを引数として、予め登録しておいた任意の述語を呼びだしてくれるものを用意すればよいのではないか」それは以下のようなクラスおよびヘルパー関数のセットとなるだろう。

// 汎用 StationAdapter 述語
template< class Func > class StationAdapter_t
{
public:
	StationAdapter_t( Func fc ): fc_( fc ){}
	template< class T > 
    void operator()( T* h ){ fc_( h->getStation() ); }
	Func fc_;
};
template< class Func >
StationAdapter_t< Func > StationAdapter( Func fc ){ 
	return StationAdapter_t< Func >( fc );
}
//

驚いたことに、上記の汎用 StationAdapter 述語は、Hillwayのみならず、およそ*->Station(); と書ける*なら遍く何でもそれを引数として、予め登録しておいた指定の任意関数を呼び出してくれる述語クラスだ。これを使ってfor_eachで書き直したプログラムは以下のようになる。

// Delete 述語
void DeleteHillway( Hillway* h ){ delete h; }
//
int main( void )
{
	vector< Hillway* > hillways;
	for( int i=0; i<12; ++i ){
		hillways.push_back( new Hillway );
	}// i
	for_each( hillways.begin(), hillways.end(), 
           StationAdapter( mem_fun(&Station::North) ) );
	for_each( hillways.begin(), hillways.end(), DeleteHillway );
	return 0;
}

いかがだろうか。for_eachで記述したいというだけの動機づけだが、そのために設計したテンプレートや関数オブジェクトが思わぬ汎用性を持っていることに気付くのは、結構楽しそうな気がしてこないだろうか。

BOOST_FOREACHや、C++0xの新しいfor 構文があるとはいえ、std::for_eachは常にみんなの友達であり続けるはずだ。便利で使いやすい汎用的な述語クラスをひとたび作ってしまえば、それはラムダ式を毎回書くよりもはるかに効率的な記述が可能となる場合すらあるわけなんだし、述語クラスとして持っておくことは何よりの抽象化の助けになる。

以下追記:
もっと驚いたことに、下記のようなHillwayAdapterクラス、

// 汎用 HillwayAdapter 述語
template< class Method, class Func > class HillwayAdapter_t
{
public:
	HillwayAdapter_t( Method meth, Func fc ): fc_( fc ), meth_(meth){}
	template< class T >
        void operator()( T* h ){ fc_( meth_( h ) ); }
	Func fc_;
        Method meth_;
};
template< class Method, class Func >
HillwayAdapter_t< Method, Func > HillwayAdapter( Method meth, Func fc ){ 
	return HillwayAdapter_t< Method, Func >( meth, fc );
}

を用意することにより、

Hillway->Func1()->Func2()

という具合に、2段階の関数呼び出しを、for_eachの第3引数に指定することが可能となってしまうのであった。これはもう、底知れず便利ですね。2段階呼び出しだから for_each は使えないということはもはや言えなくなってしまうのであった。

// Delete 述語
void DeleteHillway( Hillway* h ){ delete h; }
//
int main( void )
{
	vector< Hillway* > hillways;
	for( int i=0; i<12; ++i ){
		hillways.push_back( new Hillway );
	}// i
	for_each( hillways.begin(), hillways.end(), 
               HillwayAdapter( 
                  mem_fun( &Hillway::getStation ), 
                  mem_fun( &Station::North) 
               ) 
        );
	for_each( hillways.begin(), hillways.end(), DeleteHillway );
	return 0;
}

でも、本当はこう書きたい↓。書けるのか?
ラムダとか使うとこんなことできるのだろうか?それはラムダを勉強しないことにはわからなさそうだ。

for_each( hillways.begin(), hillways.end(), 
                               _1->getStation()->North() );