プリプロセッサでループしましょう!

みなさんいかがお過ごしでしょうか?前回は足し算しました。今回はループです。
足し算ではとにかくいっぱいPP_ADD_Iを並べましたが、もっとスタイリッシュに書きたいですね。書きましょう。

まずC/C++のループはこんな感じです:

while (condition) { ... }

forはwhileの便利記法なので、whileさえあればfor相当のものは作れます。逆も言えますが。conditionはループの判定式です。{ ... }は繰り返したい処理です。
conditionはただの変数に見えますが、別にこれはf(g(h(n)))とか、i < 3とかそういうのでもいいです。とにかく何か式です。
ちょっと困りました。whileは構文です。関数じゃありません。どうしましょう。こうしましょう。

while(p, f, v) = while'(p, f, while'(p, f,...while'(p, f, v))...);
while'(p, f, v) = if p(v) = 1 then f(v) else v

前回のadd, add'に似てますね。
「とにかくたくさん」の部分がちょっと違います。各while'の最初の引数と次の引数は常に同じで、最後の引数は直前のwhile'の結果にするのです。
while'の引数pやfがなんかp(v)とかf(v)とか、まるで関数みたいな扱いです。まぁ関数ですけどね。そうです、while'は関数を引数にとります。関数を引数にとるというのがよく分からない方はめんどいので「高階関数」でググってください。
pをループ一回ごとに続けるかどうか判定する関数、fをループ一回分の処理をする関数、vをループ内で使う値とします。while'はp(v)が1ならf(v)を、0ならvを返します。whileは、while'の結果、つまりf(v)かvに対してwhile'を使い続けます。
詳しく見てみましょう。p(v)が1ならwhile'はf(v)を返します。あるwhile'の呼び出し(例えば3回目)でp(v)が0になったとします(以後、3回目のwhile'呼び出しをwhile'3、3回目のwhile'内で使っているp, vをそれぞれp3, v3と書くことにします。仮面ライダー)。するとwhile'3はv3を返します。while'をとにかくいっぱい呼び出したいので、それをそのままwhile'の一番後ろの引数にします(この呼び出しは4回目になりますね。while'4と表記することにします。同様にwhile'4内で使っているp, vについてもp4, v4と書きます)。
p4はp3と同じです。whileがどう作られているかを見れば分かります。v4はwhile'3の戻り値と同じです。while'3の戻り値は、先に書いたようにv3です。つまり、p3とp4は同じで、v3とv4は同じで、p3(v3)という式は0になるのだから、p4(v4)もまた0になりますね。
てなわけでwhile'4は結局while'3の戻り値をそのまま返しているだけですね。ということはwhile'(5回目)もifが0になって(以下略)
平たく言うとwhile'の連続呼び出しは、p(v)が1である限りf(v)を返しますが、1度でもifで0と判定されると、以後のwhile'の呼び出しはvを返すだけです。C/C++のwhileで条件式が0になるとループを抜けるのと似てますね。でもwhile'が例えば1000個書いてあったとすれば、1000個のwhile'の途中で中断することはできないので、代わりに判定式が0になった以降ずっと同じ結果を返しつづけることにするのです。

さて、これをプリプロセッサで書いてみましょう。

#define PP_WHILE(p, f, v) PP_WHILE_I(p, f, PP_WHILE_I(p, f, PP_WHILE_I(p, f, PP_WHILE_I(p, f, PP_WHILE_I(p, f, PP_WHILE_I(p, f, PP_WHILE_I(p, f, v)))))))
#define PP_WHILE_I(p, f, v) PP_IF(p(v), f(v), v)

できました。が、説明用の式そのままです。

#define FOO(a) PP_DEC(a)
#define BAR(a) PP_BOOL(a)

こういう関数マクロがあったとして、PP_WHILE(BAR, FOO, 4)と書くと、PP_WHILE_I(BAR, FOO, PP_WHILE_I(BAR, FOO, ...PP_WHILE_I(BAR, FOO, 4))...)と展開されます。
PP_WHILE_I(BAR, FOO, 4)はPP_IF(BAR(4), FOO(4), 4)と展開されます。めんどいので省略してこれは3に展開されます。ということは次のPP_WHILE_Iは、PP_WHILE_I(BAR, FOO, 3)と展開されてあとはめんどい。
結局0になるまで1引き続けて0になったら残りのPP_WHILE_Iは0を返し続けるので、このPP_WHILEの結果は0です。つまらないですね。

これを使ってPP_ADDを書き直してみましょう。

#define PP_ADD(m, n) PP_TUPLE_ELEM(2, 1, PP_WHILE(PP_ADD_P, PP_ADD_L, (m, n)))
#define PP_ADD_P(mn) PP_BOOL(PP_TUPLE_ELEM(2, 0, mn))
#define PP_ADD_L(mn) PP_ADD_L_I mn
#define PP_ADD_L_I(m, n) (PP_DEC(m), PP_INC(n))

ちなみに説明用の式で書くとこんな感じになります。

add(m, n) = while(addp, addl, <m, n>)
addp(t) = if first(t) = 1 then 1 else 0
addl(t) = <first(t) - 1, second(t) + 1>

add''が小分けされた感じになってますね。めんどいので解説とかはないです。

確かにPP_ADDでは繰り返し書いていたのが無くなりましたが、その繰り返しはPP_WHILEの中に移動しただけです。相変わらず256までの足し算が書きたければ256回分書かないといけません。もっと多くの回数ループさせたいこともあるかもしれませんし無いかもしれません。いいえきっとあります。なのでここではとにかく大量に展開してくれる関数マクロを定義してみましょう。
そうですね、ここではだいたい1000回ぐらい展開してくれるように書いてみます。

#define PP_EXP_REC_0(f, v) f(v)
#define PP_EXP_REC_1(f, v) PP_EXP_REC_0(f, PP_EXP_REC_0(f, v))
#define PP_EXP_REC_2(f, v) PP_EXP_REC_1(f, PP_EXP_REC_1(f, v))
#define PP_EXP_REC_3(f, v) PP_EXP_REC_2(f, PP_EXP_REC_2(f, v))
#define PP_EXP_REC_4(f, v) PP_EXP_REC_3(f, PP_EXP_REC_3(f, v))
#define PP_EXP_REC_5(f, v) PP_EXP_REC_4(f, PP_EXP_REC_4(f, v))
#define PP_EXP_REC_6(f, v) PP_EXP_REC_5(f, PP_EXP_REC_5(f, v))
#define PP_EXP_REC_7(f, v) PP_EXP_REC_6(f, PP_EXP_REC_6(f, v))
#define PP_EXP_REC_8(f, v) PP_EXP_REC_7(f, PP_EXP_REC_7(f, v))
#define PP_EXP_REC_9(f, v) PP_EXP_REC_8(f, PP_EXP_REC_8(f, v))
#define PP_EXP_REC_10(f, v) PP_EXP_REC_9(f, PP_EXP_REC_9(f, v))

#define PP_EXP_REC(exp, f, v) PP_EXP_REC_I(exp, f, v)
#define PP_EXP_REC_I(exp, f, v) PP_EXP_REC_ ## exp (f, v)

なんとたったの13行(空行除く)でできました!見てみましょう!
PP_EXP_RECは、指数とfとvを引数にとって、f(f(...f(v))...)と展開します。PP_EXP_REC_0は、fとvを受けとって、f(v)へと展開します。次にPP_EXP_REC_1はPP_EXP_REC_0を2回使って定義しています。全て展開するとf(f(v))になります。なんか長ったらしいのに2回て…効率悪いですね。素直にf(f(v))とか書けばいいのに。
でも待ってください。PP_EXP_REC_2はPP_EXP_REC_1を2回使っています。PP_EXP_REC_1自体がfを2回使っているのですから、fは計4回になります。PP_EXP_REC_3はPP_EXP_REC_2を2回つまりfを8回、PP_EXP_4はPP_EXP_REC3を2回、fの回数にして16回…もうお気付きですね。指数爆発します。上に書いた10までで1024回の展開になります。一つ書きたせば2倍になるので楽に回数を確保できますね!
指数を調整できるようにしてあるので、少なくてもいいというときは減らせます。あんまり多いと展開に時間がかかるので、そこらへんはよく考えましょう。プリプロセッサでもパフォーマンスは重要です。
「関数f 1000個分の適用量 『PP_EXP_REC』」みたいな文句で売り出しましょう。買いません。

今度はこの指数回数展開してくれる関数マクロを使ってPP_WHILEを書き直してみましょう。

#define PP_WHILE(p, f, v) PP_TUPLE_ELEM(3, 2, PP_EXP_REC(10, PP_WHILE_I, (p, f, v)))
#define PP_WHILE_I(pfv) PP_WHILE_II pfv
#define PP_WHILE_II(p, f, v) (p, f, PP_IF(p(v), f(v), v))

PP_EXP_RECの最初の引数は、1引数の関数マクロでした。しかしPP_WHILE_Iの引数は3つも必要です。3個いるのに1個しか引数に取れないなんて!合体しよう!というわけでタプルを使ってめでたく1個の引数になりました。
さて、これでループは完成です。お好みに応じてPP_FORを作るのもよいでしょう。
おっと、今回も結果を貼っておきましょう:http://codepad.org/4uXL7Pfm
前回のコードをベースにPP_ADDの定義を変更して、今回書いたPP_EXP_RECとPP_WHILEを追加しておきました。それとさすがに数が0, 1, 2, 3だけだと狭すぎるので、PP_INC_nとPP_DEC_nも増やしました。

さて、ここまでで分かる通り、プリプロセッサはちゃんと数値を計算することができて、分岐と限定的にですがループを扱うことができます。つまり、プリプロセッサプログラミング言語だと言ってもいいのではないでしょうか!
みなさんも、プリプロセッサコンパイル時にコード切り替えたり、#defineで定数を定義するしかできない能無しだと思っている人を見かけたら、心の中で「ぷぷぷ、プリプロセッサの能力を活かしきれていないかわいそうな人達れす」とほくそえむと良いでしょう。ただし顔に出すのはトラブルの元なので止めておきましょう。

それと一つ注意です。各コンパイラ屋さんによってプリプロセッサの実装が微妙に違ったりするため、サンプルのようにうまく動かない場合もあります。そのへんは勘弁してね(心臓)。ただ、だからと言って諦めるのは早すぎます。もっと落ち着いてください。
ここで示した考え方はだいたい通じるので、今まで何度かでてきた#define PP_XXX(a) PP_XXX_I(a)とかの部分を工夫したりすれば割と動きます。このへんに関してはBoost.Preprocessorというライブラリがあるので、そのソースコードを調べてみてください。一気に難度あがりましたね。
もっともこの解説自体がBoost.Preprocessorがどんな感じに作られているかの解説だったりするんですけどね。むしろこのへんのことをそれなりに解説している文書が見つからなかったので、ならば私が書こうではないかということで書いた次第でありますが!
ちなみにBoost.Preprocessorにはほかにも面白いものがあったりしますが、めんどいので気が向いたら書く気がします。