「超汎用関数ポインタ」のコメントより
boost::functionは難しくてギブアップ・・・
可変長のオーバーロードをどうやって解決しているのか
が知りたかったんだけど、
(以下略)
C++ の真骨頂はプリプロセッサとテンプレートを駆使したメタプログラミングにある。メタプログラミングこそが、C++ を他言語の追随を許さない超言語に押し上げていると共に、比類なき難度を持った言語にしている。
boost::function も、そこそこにメタしている実装のひとつだ。
とりあえず、プリプロセスされていない状態では理解が難しいので、プリプロセスだけ実行する。そうすれば格段にソースコードは読み易くなる。Visual C++ なら「/E」「/PE」「/EP」コンパイラオプションを使うと、プリプロセスした結果が得られる。
以下は、boost::function をプリプロセスした結果の抜粋だ(実装は省略)。
template<typename R, typename Allocator = std::allocator<function_base> >
class function0 : public function_base{
}
template<typename R, typename T0, typename Allocator = std::allocator<function_base> >
class function1 : public function_base{
}
template<typename R, typename T0, typename T1, typename Allocator = std::allocator<function_base> >
class function2 : public function_base{
}
要するに、上記のようなテンプレートが N 個できる(デフォルトでは 10 個。つまり引数 10 まで対応できる)。テンプレートの部分特殊化や関数型テンプレートパラメータに対応していないコンパイラでは、これら function0、function1 といった引数の数に応じて名前の違うテンプレートを使用する。
古いコンパイラでないならば、boost::function が以下のように使えるのは前回の通り。
#include <boost/function.hpp>
using namespace boost;
bool func0(){
return true;
}
bool func2(int i1, int i2){
return true;
}
int main(){
function<bool (void)> f0 = func0;
function<bool (int, int)> f2 = func2;
return 0;
}
さて、どのようになっているのか?
テンプレートの部分特殊化と関数型テンプレートパラメータ(※1)
先程のプリプロセスの結果には以下のようなコードもある(実装は省略)。
template<typename R, typename Allocator>
class function<R (void), Allocator> : public function0<R, Allocator>{
}
template<typename R, typename T0, typename Allocator>
class function<R (T0), Allocator> : public function1<R, T0, Allocator>{
}
template<typename R, typename T0, typename T1, typename Allocator>
class function<R (T0, T1), Allocator> : public function1<R, T0, T1, Allocator>{
}
上記のテンプレートが N個できる(デフォルト 10 個)。これがテンプレートの部分特殊化である。
テンプレート部分特殊化を使うとテンプレートは以下のように書ける。テンプレートの特殊化とは少々違う。
// 普通のテンプレート
template<typename T>
class clazz{
};
// T がポインタの場合だけ特別に使うテンプレート
template<typename T>
class clazz<T*>{
};
また、テンプレートパラメータを関数型として特殊化(部分特殊化ではない)できる。
typedef void (*f)(void);
// テンプレートパラメータを void (*f)(void) の関数型に特殊化。
template<>
class clazz<f>{
};
テンプレートの部分特殊化としてテンプレートパラメータを関数型にすると以下のようになる(多分このような解釈)。
// 戻り値型が R、引数型が T0 の関数型として部分特殊化
template<typename R, typename T0>
class clazz<R (T0)>{
};
従って、boost::function はプリプロセッサで複数の(デフォルトでは 10 個)テンプレート部分特殊化した定義により、可変長引数に対応している。
以下は、実装を非常に単純にして、テンプレート部分特殊化(関数型テンプレートパラメータ)を示したコード。
#include <iostream>
using namespace std;
template<typename R>
class function0{
public:
void operator()() const{
cout << "call arg0" << endl;
}
};
template<typename R, typename T0>
class function1{
public:
void operator()() const{
cout << "call arg1" << endl;
}
};
template<typename R, typename T0, typename T1>
class function2{
public:
void operator()() const{
cout << "call arg2" << endl;
}
};
template<class T>
class function;
template<typename R>
class function<R (void)> : public function0<R>{};
template<typename R, typename T0>
class function<R (T0)> : public function1<R, T0>{};
template<typename R, typename T0, typename T1>
class function<R (T0, T1)> : public function2<R, T0, T1>{};
int main(){
function<bool (void)> f0;
function<bool (int)> f1;
function<bool (int, int)> f2;
f0(); // 出力 : "call arg0"
f1(); // 出力 : "call arg1"
f2(); // 出力 : "call arg2"
return 0;
}
※1 「関数型テンプレートパラメータ」とは私が勝手に命名したもの。正式な名称は分からない。誰か教えて。