mem_funについて

後日記:
ていうかC++0xC++11といったものに対応し始めてるVisualStudio2010以降やXCodeなどではもはやラムダ式が使えるので、
本記事の扱うmem_funや、bindなど、この辺の関数アダプタ君たちはもう軒並み必要なくなってくる感じかと思われます。
C++11については、「C++ポケットリファレンス」などの本を見てもらうとして、mem_funやbindなど、それしかない時代は便利でしたが、
分かるまでは分かりにくいし、ラムダ式のほうがずっと分かりやすくて多機能なので、本記事の存在価値はほぼなくなりました。
なのでmem_funとかbindとか知らない人はうっかり勉強してしまわないように気をつけましょう。ラムダ式覚えましょう。
C++ ポケットリファレンス

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

mem_fun()は、メンバ関数を、あたかも、メンバ関数のように呼び出すために、C++の標準ライブラリに用意されたカラクリだ。例えば、

class A{
public:
      int hi( void ){ return 77; }
      int hi2( int a ){ return a; }
};

というクラスがあったとしよう。mem_fun()や、mem_fun1()を使うと、

#include <iostream>
#include <functional> // ←mem_funを使うために必要
using namespace std;

A a;

a.hi();   // や、
a.hi2( 88 );// として、呼び出していたところを、

mem_fun( &A::hi )( &a );// としたり、
mem_fun1( &A::hi2 )( &a, 88 );// として、呼び出せるのである。

// あるいは、↓のように、関数オブジェクトを明示的に作成しておき、
mem_fun_t<int, A> fn( &A::hi );
mem_fun1_t<int, A, int> fn2( &A::hi2 ); 
fn( &a );// や、
fn2( &a, 88 );// として、呼び出すこともできる。

mem_funにはmem_fun_refやmem_fun1、boost::mem_fnなど、多くのファミリーがいるが、その目的は概して以上が全てだ。これがナゼ便利なのか?利点は?
…mem_funが重宝される理由は、主に各種アルゴリズムSTLのそれに限らずオリジナルのものも含めて!)の汎用述語に、クラスのメンバ関数も指定できるようになるからだ。更に、bind1st, bind2nd、boost::bindなどといった機能と組み合わせることによって既存アルゴリズムが利用できるケースの幅が一気に広がり、再利用性が格段に増すのである。
「それにしてもナゼこのような奇妙なことができるのだろうか?そもそも、この奇妙な書式は何だ?」そんな疑問を解決するためには、mem_funライクなカラクリを自分で実際に作成してみるのが一番早い。
mem_fun ライクな機能を自前で実装するための、唯一の難関は、メンバ関数へのポインタの扱いだ。それは以下のようになる。

// メンバ関数のポインタの基本
//(知らないと書けないけど知ってればこれだけのことだ)
A a;
int (A::*pp)() = &A::hi;// メンバ関数ポインタ変数ppの作成と代入
cout<< (a.*pp)() <<endl;// ppの呼び出しの仕方

これで一気に難関を突破した。このようなことが出来るとわかれば誰でも、下記のような関数オブジェクトテンプレートを記述することを思いつくことが出来るのではないだろうか?

template< class S, class T > 
class MyMemfun_t
{
public:
      explicit MyMemfun_t( S (T::*p)(void) ){ m_func = p; }
      S operator()( T* p ){ return (p->*m_func)(); };
private:
      S (T::*m_func)(void);
};

あとは、テンプレート引数を省略したいがために、関数テンプレートを用意する。MyMemfunは、MyMemfun_tのインスタンスを作成して、返すだけの関数テンプレートなだけだ。(これをヘルパー関数と呼んだりする)

template< class S, class T > 
MyMemfun_t< S, T > MyMemfun( S (T::*p)(void)){
      return MyMemfun_t< S, T>( p );
}

上記で作成したMyMemfunおよびMyMemfun_tは、冒頭で見た標準ライブラリのmem_funやmem_fun_tと全く同じ使い方が可能だ。もちろん、for_each やその他アルゴリズムの1項(unary)述語引数としても全く問題なく使用できる。引数を1つだけとるmem_fun1の自作版の方については、各自で作成されてみてはいかがだろうか?上記を修正して簡単に実装できるだろう。
(☆実際には、(T::X*)(void)の、void の部分が、void以外の1引数だった場合は、それがint, char何であっても良いように、上記の、(T::X*)(void) 型の部分そのものを、1文字のテンプレートパラメータとすべきだ。が、ここではmem_funの完全版を実装することを目的としてるわけではないのでこれ以上の深入りはしない。)

つまり、mem_fun の不思議な感じとは、メンバ関数へのポインタ表記、関数オブジェクトテンプレートと、関数テンプレートのハーモニーのなせる業だったのである