投稿数 - 438, コメント - 2423, トラックバック - 155

C++ - boost::function はどのように可変長引数に対応しているのか

「超汎用関数ポインタ」のコメントより

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 「関数型テンプレートパラメータ」とは私が勝手に命名したもの。正式な名称は分からない。誰か教えて。

投稿日時 : 2008年1月7日 22:06

フィードバック

# re: C++ - boost::function はどのように可変長引数に対応しているのか

私の場合、functionNは作らずにマクロでムリヤリ作っちゃいました

Boost.FunctionはPreprocessorを使って可変個の特殊化を一気に作ってますね
(おかげですごい読みにくい…)

あまりいい例ではないですけど、私が作ったfunctionはここにあります
http://d.hatena.ne.jp/faith_and_brave/20071031/1193831871

>「関数型テンプレートパラメータ」
んー、こういう書き方はBoost.Functionで初めてみたのでわかんないです(><)
2008/01/07 22:51 | アキラ

# re: C++ - boost::function はどのように可変長引数に対応しているのか

コメントにあったのはどうやらPreprocessorでの可変個の特殊化の詳細が知りたいようだったので

>BOOST_FUNCTION_PARMS
>BOOST_FUNCTION_ARGS
これは、Boostが提供しているマクロですが、Boost.Functionはこれらを使って可変個の特殊化を作成しています

私はたぶん同様のことを以下のようにして行いました

#define SHAND_TEMPLATE_PARAMS_1 class T0
#define SHAND_TEMPLATE_PARAMS_2 class T0, class T1
...

#define SHAND_TEMPLATE_ARGS_1 T0
#define SHAND_TEMPLATE_ARGS_2 T0, T1
...

#define SHAND_FUNCTION_PARAMS_1 T0 t0
#define SHAND_FUNCTION_PARAMS_2 T0 t0, T1 t1
...

#define SHAND_FUNCTION_ARGS_1 t0
#define SHAND_FUNCTION_ARGS_2 t0, t1
...

#define SHAND_FUNCTION_CLASS_N(Count) \
template <class R, SHAND_TEMPLATE_PARAMS_##Count##> \
class function<R (SHAND_TEMPLATE_ARGS_##Count##)> \
...


こういうマクロを作って
↓で一気に10個分のfunction作成
SHAND_FUNCTION_CLASS_N(1)
SHAND_FUNCTION_CLASS_N(2)
SHAND_FUNCTION_CLASS_N(3)
SHAND_FUNCTION_CLASS_N(4)
SHAND_FUNCTION_CLASS_N(5)
SHAND_FUNCTION_CLASS_N(6)
SHAND_FUNCTION_CLASS_N(7)
SHAND_FUNCTION_CLASS_N(8)
SHAND_FUNCTION_CLASS_N(9)
SHAND_FUNCTION_CLASS_N(10)
2008/01/07 23:02 | アキラ

# re: C++ - boost::function はどのように可変長引数に対応しているのか

>コメントにあったのはどうやらPreprocessorでの可変個の特殊化の詳細が知りたいようだったので

お~ぅ。確かにそっちかも。

>BOOST_FUNCTION_PARMS
>BOOST_FUNCTION_ARGS

で鬼読みにくくなってますよねぇ。
2008/01/07 23:32 | 囚人

# re: C++ - boost::function はどのように可変長引数に対応しているのか

>あまりいい例ではないですけど、私が作ったfunctionはここにあります
>http://d.hatena.ne.jp/faith_and_brave/20071031/1193831871

お~!相変わらずスゴイですね。
2008/01/08 0:01 | 囚人

# re: C++ - boost::function はどのように可変長引数に対応しているのか

いえいえ、古いコンパイラには非対応なので
Preprocessorさえ読み解けばなんとか作れますw
2008/01/08 0:13 | アキラ

# re: C++ - boost::function はどのように可変長引数に対応しているのか

やはり自分のプリプロセスの知識・解釈・技量不足
でしたかね・・・。

10個とはわかりませんでしたが、数の制限は
あるのではないかと推測してみていたので、
その裏付けとしてマクロを展開してみたのですが、

たとえばreturn (*f)(BOOST_FUNCTION_ARGS);は
return (*f)(BOOST_PP_CAT(BOOST_PP_REPEAT_, BOOST_PP_IIF(1 BOOST_PP_TUPLE_EAT_3(3, 0, BOOST_PP_NIL), BOOST_PP_NODE_1, BOOST_PP_NODE_3)(BOOST_PP_REPEAT_P))(BOOST_FUNCTION_NUM_ARGS, BOOST_PP_ENUM_PARAMS_M, a));

みたいにこれ以上展開できなくなったのでギブアップ。。
既に間違っている? レベル低いですよね。悲しい・・・
言い訳になるかもしれませんが、マクロは大嫌いです;
個人的にはインクルードや、pragmaとかのしょうがないものや、
インクルードガードとコンパイルのスィッチ
以外での目的で使わないで欲しいと思ってしまいます(ぇ。

>Visual C++ なら「/E」「/PE」「/EP」コンパイラオプションを使うと、プリプロセスした結果が得られる。

なるほど、これは知りませんでした!
いつもわかりにくいマクロは手動で展開して
見ていたのですが、
展開結果はそんなことをせずに見ることが出来るのですね;
後で調べてみようと思います。
展開後のコードでステップ実行も出来るとすごくうれしいのですけどw

それにしてもわざわざ解説して頂き、
どうもありがとうございました。
さすがにお二人ともめちゃくちゃ詳しいですね。
自分のまわりには自分よりC++がわかる人
って一人もいないのでとても勉強になります。
2008/01/10 17:22 | 通りすがり

# re: C++ - boost::function はどのように可変長引数に対応しているのか

>言い訳になるかもしれませんが、マクロは大嫌いです;
>個人的にはインクルードや、pragmaとかのしょうがないものや、
>インクルードガードとコンパイルのスィッチ
>以外での目的で使わないで欲しいと思ってしまいます(ぇ。

私も好きじゃないですねぇ。デバッグしててもわけがわかりませんもんね。


>さすがにお二人ともめちゃくちゃ詳しいですね。
>自分のまわりには自分よりC++がわかる人
>って一人もいないのでとても勉強になります。

アキラさんは明らかに(←ここかけてみた)すごいですが、私はまだまだ C++ 使いとは言えません。
C++ は難しいですねぇ。その分楽しいですけども。
2008/01/11 11:04 | 囚人

# re: C++ - boost::function はどのように可変長引数に対応しているのか

マクロは、コンパイルエラーがどこで出てるのかわからなくなるのでキライですね~
できれば使いたくない

>アキラさんは明らかに(←ここかけてみた)すごいですが
いえいえ、職場はともかくネット上には神レベルのひとがいっぱいいるので
ぼくなんてまだまだですよー

Boostのソースを読むとかなりのレベルアップになるのでお試しあれ(それを作るとさらにレベルアップ!)
2008/01/11 19:22 | アキラ

コメントの投稿

タイトル  
名前  
URL
コメント