任意型の機能演算子を受け取るアルゴリズムの実装

やあ子供たち。元気だったかい。最近おじさんは粒子法のことばかり気になっていて、世の中のものは皆粒子でできているし、皆粒子が集まって生まれ、やがていつかはみんな粒子に返るんだという世界観を確立したよ。つまり世の中には粒子しかなくて、人間とかが考えたりしてるんじゃなくて実は粒子がぐるぐる回っているだけっていう、そういう世界観だよ。
さて、今日は

  • 任意コンテナ型を受け取ることのできる機能演算子(関数オブジェクト)と、
  • それを利用できるアルゴリズムを実装

するための勉強だよ。
良く似た複数のアルゴリズムを書いていて、ループの構造自体は同じなんだけど、そのループの中の処理だけが違っていて、それがために同じような関数を複数書いてしまった!なんて経験はないかい。そんなときは、アルゴリズムを関数テンプレートとして用意して、ループの中身の処理は、機能演算子として、呼び出し側で指定できるようにすると便利だよ。
例えば以下の例では、任意の数列コンテナの中身に対して、ベースライン補正した値を表示するアルゴリズムを考えているよ。ベースライン補正するやり方はたくさんあるね。全ての値を最小値からの相対値にするとか、平均値からの相対値にするとか。。
以下のプログラムでは、ベースラインの表示のアルゴリズムとして関数テンプレートを用意し、ベース値(平均値、最小値など、、)の計算方法は任意コンテナ対応の、機能演算子として外だしすることにより、任意のベース値算出法を、演算子として指定できるようにしてみたよ。

#include <iostream>
#include <vector>
#include <list>
using namespace std;

class UseFront
{
	// 任意コンテナ型の、先頭要素を、返す演算子の定義。
public:
	template< class T > 
	typename const T::value_type operator()( const T& t ) const{
		return t.front(); 
	}
};

class UseBack
{
public:
// 任意コンテナ型の、末尾要素を、返す演算子の定義。
  template< class T >
  typename const T::value_type operator()( const T& t ) const{
    return t.back(); 
  }
};

class UseAverage
{
public:
  // 任意コンテナ方の、平均を、返す演算子の定義。
  template< class T >
  typename const T::value_type operator()( const T& t ) const {
    T::value_type avg=0;
    for( T::const_iterator it = t.begin(); it!=t.end(); ++it ){
      avg += *it;
    }// it
    return avg/t.size();
  }
};


// ベースライン補正アルゴリズム
// (Funcな方法で計算した値:ベースからの、相対値を表示
template< class T, class Func > 
typename void lc_calibrate( const T& t, const Func& func ){
  for( T::const_iterator it=t.begin(); it!=t.end(); ++it ){
    cout << *it-func(t) <<", ";
  }// it
  cout<< endl;
}


int main( void )
{
  {
    vector< int > a;// 整数型のvectorでやってみよう
    a.push_back( 7 );
    a.push_back( 8 );
    a.push_back( 9 );
    a.push_back( 10 );
    lc_calibrate( a, UseFront() );// 先頭要素からの相対値で表示
    lc_calibrate( a, UseBack() );// 末尾要素からの相対値で表示
    lc_calibrate( a, UseAverage() );// 平均からの相対値で表示
  }
  {
    list< float > a;// 今度はfloat型のlistコンテナだよ
    a.push_back( 7 );
    a.push_back( 8 );
    a.push_back( 9 );
    a.push_back( 10 );
    lc_calibrate( a, UseFront() );// 先頭要素からの相対値で表示
    lc_calibrate( a, UseBack() );// 末尾要素からの相対値で表示
    lc_calibrate( a, UseAverage() );// 平均からの相対値で表示
  }
  return 0;
}

(気付いたと思うけど任意コンテナ型とはいっても、front(), back(), value_typeのようなインタフェースを持つことが前提となってはいるよ。こういうのはやがてやって来るC++0xとかではコンセプトっていうらしいね。)
このプログラムの実行結果は以下のようになるよ。

0, 1, 2, 3,
-3, -2, -1, 0,
-1, 0, 1, 2,
0, 1, 2, 3,
-3, -2, -1, 0,
-1.5, -0.5, 0.5, 1.5,

どうだい、便利そうな感じがしてきただろう?こうしておけば、アルゴリズムも機能演算子も、どっちもそれ自体が単独でいろんな場面で再利用できるというわけさ。
最後に任意コンテナ型を受け取る機能演算子についてだけど、これはどうして非テンプレート型クラスのテンプレートメソッドとして実装してるかわかるかな?
それは、

  • アルゴリズムに渡す段階では実体化されている必要がある、(でなきゃ渡せない!)
  • 呼び出し時には何が(どんなコンテナ型が)くるかわからない、という要望に答えなきゃいけない。

だからテンプレートメソッドとして用意する必要があるんだね!
3次元とか2次元のベクトル演算のライブラリとか、ポリゴンの中心座標求めたりだとか、いろんなアルゴリズムをライブラリ化したものを、ここまで読んでくれた子供たちもみんな作ってると思うけど、そういうのに活用してみたらぐっと楽しくなるんじゃないかな。じゃあ今日はこの辺でおじさんは失礼するよ。
またな!