プリプロセッサで足し算しましょう!

プリプロセッサは所詮文字列置換、計算なんてできやしない…そう思っている方は多いでしょう。しかしそれは違います。計算できます。
本日の目的はプリプロセッサで足し算です。ADD(3, 2)と書けば5と答えてくれる関数マクロを作ってみましょう。
おっと、#define ADD(m, n) ((m) + (n)) とかではありません。これで足し算をするのは誰か、それはコンパイル・実行したときの処理系です。
今日の目的はあくまでも、プリプロセッサという処理系に閉じた足し算を定義することです。言い換えればプリプロセスが終わった時点で結果がでていないといけません。あとなんか細かいことはある感じがしますが、もうめんどいので早速始めましょう。

まずプリプロセッサにおける数を取り決めておきます。数とは、数字のみで構成されたトークンのことです。数字とは、トークンとは何かなどはめんどいので常識的に考えてください。
ただしめんどいので今回は0, 1, 2, 3だけにしておきます。

数は決まりました。では足し算の見た目を決めましょう:

PP_ADD(1, 2)

この結果が3になればいいのです。ここで言う3はさきほど決めた数としての3です。それ以外は3ではありません。
プリプロセッサにこれをさせるにはどう書けばいいのでしょう?こんな方法があります:

#define PP_ADD_0_0 0
#define PP_ADD_0_1 1
#define PP_ADD_0_2 2
#define PP_ADD_0_3 3
#define PP_ADD_1_0 1
#define PP_ADD_1_1 2
...(省略されました。全てを閲覧するにはご自分でお書きください)

#define PP_ADD(m, n) PP_ADD_I(m, n)
#define PP_ADD_I(m, n) PP_ADD_ ## m ## _ ## n

こうすれば、PP_ADD(1, 2)はPP_ADD_1_2と展開されて、3となります。要は足し算テーブルを作っただけです。
PP_ADD(3, 3)はどうなるんだ普通は6やんけでも6とか今は定義されてへんやんけ!という件については、テーブルを大きくして対処してください。でもどのみちテーブル外になる組合せはできます。いくら計算するといっても相手はプリプロセッサです。少しはいたわってあげましょう。
ここではめんどいので計算結果がさっき決めた数の中に閉じておいてほしいので、3以上になる計算は全て3とします。

あとPP_ADD_Iの存在意義についてですが、PP_ADD(1, PP_ADD(2, 1))とかできるようにするためです。PP_ADDを直接PP_ADD_ ## m ## _ ## nと定義してしまうと、PP_ADD(1, PP_ADD(2, 1))はPP_ADD_1PP_ADD(2, 1)とかに展開されてしまうので、ワンクッションおいて内側の式を全て展開しておこうという魂胆です。

ところで、この足し算テーブルを作る方法には弱点があります。今回は3まで定義しているだけなので、PP_ADD_m_nというのを16個書けばいいだけですが、256まで扱うとすると65536個も書かないといけません。面倒ですね。
「適当なスクリプト言語で生成すればいいやん」というのは却下です。テーブルだけで65000行になります。でかすぎます。帰ってください。

そこでもっと単純な定義を組み合わせなんとかする方法をご紹介しましょう。むしろこっちが本番です。
まずは1足す関数を作ります。

#define PP_INC_0 1
#define PP_INC_1 2
#define PP_INC_2 3
#define PP_INC_3 3

#define PP_INC(n) PP_INC_I(n)
#define PP_INC_I(n) PP_INC_ ## n

例によって3を超える結果になる計算は3としておきます。あとPP_INC(PP_INC(0))とかも計算できるようにしておきます。
ついでなので1引く関数も作ってしまいましょう。

#define PP_DEC_0 0
#define PP_DEC_1 0
...(省略されました。全てを閲覧するにはご自分でお書きください)

0を下回る結果になる計算は0としておきます。あとはめんどいので省略します。
突然ですが足し算は次のような定義をもとに作っていきます。

add(m, n) = if m = 0 then n else add(m - 1, n + 1)

add(m - 1, n + 1)を繰り返せばmが0になったときのnが足し算の結果になります。
しかしこのままでは問題があります。プリプロセッサでは再帰的な関数を定義できないのです。
再帰的な定義ができないなら自分で展開すればいいじゃない!

add(m, n) = second(add'(add'(...add'(add'(<m, n>))...)))
add'(t) = add''(first(t), second(t))
add''(m, n) = if m = 0 then <m, n> else <m - 1, n + 1>

first(x) = xが<a, b>みたいなやつならa それ以外のときは知らん(この関数を使うことはできない)
second(x) = xが<a, b>みたいなやつならb それ以外のときは知らん(この関数を使うことはできない)

なんかヘンな式が登場していますが、一個ずつ見ていきましょう。あ、これはプリプロセッサでの式ではなく、説明用の式です。めんどいのでなんとなく理解してください。
まずaddは、ひたすらadd'の結果にadd'適用しつづけるというような感じになっています。...の部分は、「とにかくいっぱい」という意味で、とにかくいっぱいadd'を適用します。
<m, n>とは何でしょう?タプル?リスト?いいえ、ケフィアです。違います。タプルといいます。<m, n>は一つの値です。Cでいう構造体みたいなものです。
add'では、引数のタプルを解体しているだけです。別になくてもいいです。でもないと書くのがめんどいので作ります。firstは<なんとか, かんとか>というタプルが引数なら、そのうちの「なんとか」を返します。secondは「かんとか」を返します。<なんとか, かんとか>というような形じゃない引数は知りません。
add''の定義はもともとのaddと似ていますね。式中に<m, n>とか<m - 1, n + 1>というタプルがありますが、これがadd''の結果です。add''(m, n)は、<m, n>とか<m - 1, n + 1>を返します。そしてそのままadd'の引数にしちゃいます。こうしてとにかくたくさんadd'を使いまくりまくりすてぃ。
プリプロセッサに戻ります。プリプロセッサにおけるタプルは、トークンをいくつかカンマで区切ってかっこで囲ったものとします。(1, 2)や(!, $ %fo ox., オレオレ詐欺)はタプルです。タプルですが、カンマで区切っているトークンの数が違います。前者は2つで後者は3つです。ここでは「2要素のタプル」「3要素のタプル」と呼びます。
(!, $ %fo ox., オレオレ詐欺)をさっきの式で書くと、<!, $ %fo ox., オレオレ詐欺>です。0番目の要素は「!」、1番目の要素は「$ %fo ox.」、2番目の要素は「オレオレ詐欺」の3要素のタプルです。
また、本来は違うと思いますが、ここではタプル自身もトークンとして扱います。つまり(1, 2, (!, $ %fo ox., オレオレ詐欺))は3要素のタプルです。さっきの式で書くと<1, 2, <!, $ %fo ox., オレオレ詐欺>>です。
0要素のタプルは別にいらないしめんどいので定義しないことにします。
では、この定義でadd(1, 2)がちゃんと計算できるか確かめてくだしあ><

次はさっきの式をプリプロセッサ上で定義していきます。
まずはifです。まさかifも定義していないのにadd'とか定義しようなんて考えていないですよね?片腹痛いです。
プリプロセッサのifディレクティブ(#if, #ifdefなど)ではいけません。こやつは展開前に定義を変更することはできますが、展開中にどうこうすることはできないのです。体重測定の前、常日頃からダイエットすることはできても、測定中の体重計の上ではどうにもならないのと同じです。
でも今は体重はかっているわけではないので、計算中にifで分岐をする方法を作ります。

#define PP_IF_0(t, f) f
#define PP_IF_1(t, f) t

#define PP_IF(c, t, f) PP_IF_I(c, t, f)
#define PP_IF_I(c, t, f) PP_IF_ ## c (t, f)

めんどいのでこれでいいでしょう。PP_IFはcが1ならtを、0ならfを返します。
ついでに0と1をブーリアンと呼ぶことにして、数とブーリアンの間に対応を作りましょう。

#define PP_BOOL_0 0
#define PP_BOOL_1 1
#define PP_BOOL_2 1
#define PP_BOOL_3 1

#define PP_BOOL(n) PP_BOOL_I(n)
#define PP_BOOL_I(n) PP_BOOL_ ## n

はい。次にタプルを扱う仕組みを作っておきましょう。扱うといってもここではn要素タプルからi番目のトークンを取り出すだけです。

#define PP_TUPLE_ELEM_1_0(a) a
#define PP_TUPLE_ELEM_2_0(a, b) a
#define PP_TUPLE_ELEM_2_1(a, b) b
#define PP_TUPLE_ELEM_3_0(a, b, c) a
#define PP_TUPLE_ELEM_3_1(a, b, c) b
#define PP_TUPLE_ELEM_3_2(a, b, c) c

#define PP_TUPLE_ELEM(n, i, tup) PP_TUPLE_ELEM_I(n, i, tup)
#define PP_TUPLE_ELEM_I(n, i, tup) PP_TUPLE_ELEM_ ## n ## _ ## i tup

とりあえず3要素まで定義しました。
PP_TUPLE_ELEM(3, 1, (イッ, エーイ!, どんくんでーす!))は、PP_TUPLE_ELEM_3_1 というトークンの横に(イッ, エーイ!, どんくんでーす!)を並べます。するとこれはPP_TUPLE_ELEM_3_1 (...)という形になるので、関数マクロとして扱われます。うまいこと考えたものですね。
PP_TUPLE_ELEM_3_1(イッ, エーイ!, どんくんでーす!)は、エーイ!と展開されます。
さて、ようやく足し算を作るための部品ができてきました。以下のように書けるでしょう:

#define PP_ADD(m, n) PP_TUPLE_ELEM(2, 1, PP_ADD_I(PP_ADD_I(PP_ADD_I(PP_ADD_I(PP_ADD_I(PP_ADD_I(PP_ADD_I(PP_ADD_I((m, n))))))))))
#define PP_ADD_I(tup) PP_ADD_II tup
#define PP_ADD_II(m, n) PP_IF(PP_BOOL(m), (PP_DEC(m), PP_INC(n)), (m, n))

できました!
まずPP_ADDは、自身に渡された引数をタプルにしてPP_ADD_Iに渡します。PP_ADD_Iは、渡されたタプルをPP_ADD_IIというトークンの横に並べます。これは展開されてPP_ADD_II (...)となり、タプルのときと同様に展開されます。
PP_ADD_IIは、mが0なら(m, n)を、そうでないなら(PP_DEC(m), PP_INC(n))と展開されます。
こうすればmが0になるまで1引きまくり足しまくりうはうはです。でもmが0になったらそれ以上いくらADD_Iがあっても(m, n)のまま。あとはADD_Iの結果のタプルから2番目の要素を取り出せば計算結果になっています。
これが正しく動く様子も示しておきましょう:http://codepad.org/gsIiHKwA

どうですか?プリプロセッサでもちゃんと計算できるでしょう?ところでこれ、PP_ADDの定義がちょっと面倒です。いっぱいADD_Iを並べています。今は数が3までしかないので最低4回並べればいいのですが、数が256まであると256回並べないといけないですし、何よりも再帰の代わりにひたすら並べるというのはよく使いそうです。そこで次回はこの繰り返しをライブラリ化したいと思います。
ではごきげんよう