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は触らない!

*1:実は、Boost.FusionのBOOST_FUSION_ADAPT_STRUCTの一部抜粋

*2:実は、Boost.PreprocessorのBOOST_PP_CATの一部抜粋