C Preprocessorの闇に触れてみた
ちょっと C Preprocessor をやってみたくて、次のコードを書いてみた。*1
#define SEQ_TO_SEQ(X) SEQ_TO_SEQ_I(0)X ## _END #define SEQ_TO_SEQ_I(X) SEQ_TO_SEQ_0 #define SEQ_TO_SEQ_0(X) ((X)) SEQ_TO_SEQ_1 #define SEQ_TO_SEQ_1(X) ((X)) SEQ_TO_SEQ_0 #define SEQ_TO_SEQ_0_END #define SEQ_TO_SEQ_1_END SEQ_TO_SEQ( (1) (2) (3) ) // => ((1)) ((2)) ((3))
これは、括弧で括られた数字を、二重括弧にするマクロである。
さて、これは次のように評価されると期待していた。
SEQ_TO_SEQ( (1) (2) (3) ) // SEQ_TO_SEQ_I(0)(1)(2)(3) ## _END [展開] // SEQ_TO_SEQ_I(0)(1)(2)(3)_END [連結] // SEQ_TO_SEQ_0(1)(2)(3)_END [展開 (0)の消滅] // ((1)) SEQ_TO_SEQ_1(2)(3)_END [展開 (1)を外へ] // ((1)) ((2)) SEQ_TO_SEQ_0(3)_END [展開 (2)を外へ] // ((1)) ((2)) ((3)) SEQ_TO_SEQ_1_END [展開 (3)を外へ] // ((1)) ((2)) ((3)) [展開 ENDを消す]
さて、結果は・・・
SEQ_TO_SEQ( (1) (2) (3) ) // => ((1)) ((2)) ((3)) SEQ_TO_SEQ_1_END
あれ、最後の展開が発動しない・・・
なーぜー
展開 と 連結 は、どっちが先?
まず、次のコードを試してみた。
#define A(X) B ## C(X) #define C(X) 1 #define BC(X) 2 A(0) // B1 or 2 ? // => 2
このことから、連結の方が優先度は上のようだ。
連結は遅延評価できないの?
では、先に展開する方法は無いのだろうか?
ということで、連結を代理するマクロを書いて試してみた。
#define CAT(a, b) a ## b #define A2(X) CAT(B, C(X)) A2(0) // => 2
どうやら、これでは駄目なようだ。
では、たらい回してみよう。*2
#define CAT(a, b) CAT_T(a, b) #define CAT_T(a, b) a ## b #define A2(X) CAT(B, C(X)) A2(0) // => B1
お、たらい回したら、出来た。
Google先生に聞こう
と、ここまで書いて、次の文章を見つけた。
The C Preprocessor
「もっと正確に言うなら、文字列化と連結では、書かれた通りの引数を事前走査無しに使うのである。」
どうやら、連結が現れる場合、引数は即値評価されないようだ。
書きなおしてみた
ということで、連結を遅延させてみた。
#define CAT(a, b) CAT_T(a, b) #define CAT_T(a, b) a ## b #define SEQ_TO_SEQ(X) CAT(SEQ_TO_SEQ_I(0)X , _END) #define SEQ_TO_SEQ_I(X) SEQ_TO_SEQ_0 #define SEQ_TO_SEQ_0(X) ((X)) SEQ_TO_SEQ_1 #define SEQ_TO_SEQ_1(X) ((X)) SEQ_TO_SEQ_0 #define SEQ_TO_SEQ_0_END #define SEQ_TO_SEQ_1_END SEQ_TO_SEQ( (1) (2) (3) ) // => ((1)) ((2)) ((3))
おぉ、出来た!
最後が残った理由は、連結が先に実施されたことで、「SEQ_TO_SEQ_1_END」が展開対象にならなかったことが原因っぽい。
(たぶん、マクロの展開対象が「SEQ_TO_SEQ_1_END」じゃなくて「SEQ_TO_SEQ_1」と「_END」に分離されたのかも)
(そりゃ、展開結果は関数じゃないからねぇ)
結論
もう、C Preprocessorは触らない!