Cプリプロセッサ: マクロの再帰禁止とトークンの展開禁止

Cプリプロセッサがマクロの再帰的な展開を許さないのは良く知られていると思う。

#define A() [B()]
#define B() {A()}

A() /* [{A()}] に展開される */

つまり、あるマクロの展開中に、それと同名のトークンが出現した場合、展開を行わない。特定のマクロに対する一時的な展開抑制だと言うことができる。

これに関連する展開抑制規則がもう一つある。あるマクロの展開中に、それと同名のトークンが出現した場合、そのトークンは二度とマクロとして展開しない*1。これは特定のトークンに対する永続的な展開抑制になっている。

#define IDENTITY(x) x
#define CALL_WITH_4(f) f(4)

CALL_WITH_4(IDENTITY) /* 4 に展開される */
CALL_WITH_4(IDENTITY(IDENTITY)) /* IDENTITY(4) に展開される */

Boost.Preprocessorを普通に使ってメタプログラミングしている限りでは、あまりこの規則に引っかかることはないかもしれない。以下は作為的な例。

#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/seq.hpp>

BOOST_PP_CAT(BOOST_PP_, CAT) /* 使えないBOOST_PP_CATに展開される */

#define OPS (BOOST_PP_SEQ_HEAD)(BOOST_PP_SEQ_TAIL)
BOOST_PP_SEQ_HEAD(OPS) /* 使えないBOOST_PP_SEQ_HEADに展開される */

一方、マクロ展開を敢えて遅らせることで擬似的に再帰したり、ラムダ式のようにマクロ名を含むデータを引き回す、といったことをする場合、この規則が邪魔になることが多い。そういう時は、望みのマクロ名に展開される別のマクロ(間接マクロ)を定義し、そちらを引き回すことで問題を回避できる。(Chaos Preprocessor Libraryは全面的にこれを行っているようだ)

/* 間接マクロ */
#define PP_HEAD_ID() BOOST_PP_SEQ_HEAD
#define PP_TAIL_ID() BOOST_PP_SEQ_TAIL

#define OPS1 (PP_HEAD_ID)(PP_TAIL_ID)
BOOST_PP_SEQ_HEAD(OPS1)() /* 使えるBOOST_PP_SEQ_HEADに展開される */

これを利用した再帰の例。

#include <boost/preprocessor/seq.hpp>
#include <boost/preprocessor/arithmetic.hpp>
#include <boost/preprocessor/facilities.hpp>
#include <boost/preprocessor/tuple.hpp>

#define SCAN(x) x
#define SCAN0(x) x
#define SCAN00(x) x
#define SCAN000(x) x
#define SCAN0000(x) x
#define SCAN00000(x) x
#define SCAN000000(x) x
#define SCAN0000000(x) x

#define FIB(s, n, a, b) \
  s BOOST_PP_EMPTY() ( \
    BOOST_PP_IF(n, FIB_ID, a BOOST_PP_IDENTITY(BOOST_PP_TUPLE_EAT(4))) BOOST_PP_EMPTY() () \
    (s##0, BOOST_PP_DEC(n), BOOST_PP_ADD(a, b), a) \
  )
#define FIB_ID() FIB
#define FIBONACCI(n) SCAN(FIB(SCAN0, n, 1, 0))

FIBONACCI(6) /* 13 に展開される */

*1:この規則は何のためにあるんだろう