はじめに
Boost.Preprocessorの実装を調べているうちに色々出来ることが分かって楽しくなってきた。
基本的なことから曲芸的なことまで適当にパターンをメモっておきます。
シンボルの結合
#define PP_CAT(a, b) PP_CAT_I(a, b) #define PP_CAT_I(a, b) a ## b #define PP_CAT3(a, b, c) PP_CAT3_I(a, b, c) #define PP_CAT3_I(a, b, c) a ## b ## c #define X x #define Y y #define Z z PP_CAT(a, b) // => ab PP_CAT(X, Y) // => xy PP_CAT3(a, b, c) // => abc PP_CAT3(X, Y, Z) // => xyz
論理演算
数値 => 0/1
#define PP_BOOL(x) PP_CAT(PP_BOOL_, x) #define PP_BOOL_0 0 #define PP_BOOL_1 1 #define PP_BOOL_2 1 #define PP_BOOL_3 1 #define PP_BOOL_4 1 // ... PP_BOOL(0) // => 0 PP_BOOL(1) // => 1 PP_BOOL(2) // => 1 PP_BOOL(3) // => 1
否定
#define PP_NOT(x) PP_CAT(PP_NOT_, PP_BOOL(x)) #define PP_NOT_0 1 #define PP_NOT_1 0 PP_NOT(0) // => 1 PP_NOT(1) // => 0 PP_NOT(2) // => 0 PP_NOT(3) // => 0
AND/OR
#define PP_OR(x, y) PP_CAT3(PP_OR_, x, y) #define PP_OR_00 0 #define PP_OR_01 1 #define PP_OR_10 1 #define PP_OR_11 1 PP_OR(0, 0) // => 0 PP_OR(0, 1) // => 1 PP_OR(1, 0) // => 1 PP_OR(1, 1) // => 1 #define PP_AND(x, y) PP_CAT3(PP_AND_, x, y) #define PP_AND_00 0 #define PP_AND_01 0 #define PP_AND_10 0 #define PP_AND_11 1 PP_AND(0, 0) // => 0 PP_AND(0, 1) // => 0 PP_AND(1, 0) // => 0 PP_AND(1, 1) // => 1
条件分岐
#define PP_IF(b, x, y) PP_CAT(PP_IF_, PP_BOOL(b))(x, y) #define PP_IF_0(x, y) y #define PP_IF_1(x, y) x PP_IF(0, a, b) // => b PP_IF(1, a, b) // => a
引数
先頭/先頭以外の引数の取り出し
#define PP_ARG_HEAD(_0, ...) _0 #define PP_ARG_TAIL(_0, ...) __VA_ARGS__ PP_ARG_HEAD(a, b, c) // => a PP_ARG_TAIL(a, b, c) // => b, c
N個目の引数の取り出し
#define PP_ARG_GET(n, ...) PP_CAT(PP_ARG_GET_, n)(__VA_ARGS__) #define PP_ARG_GET_0(_0, ...) _0 #define PP_ARG_GET_1(_0, _1, ...) _1 #define PP_ARG_GET_2(_0, _1, _2, ...) _2 #define PP_ARG_GET_3(_0, _1, _2, _3, ...) _3 ... PP_ARG_GET(0, a, b, c) // => a PP_ARG_GET(1, a, b, c) // => b PP_ARG_GET(2, a, b, c) // => c
リスト操作
展開
#define PP_LIST_UNPACK(list) PP_LIST_UNPACK_I list #define PP_LIST_UNPACK_I(...) __VA_ARGS__ PP_LIST_UNPACK((a, b, c)) // => a, b, c
先頭の要素、先頭を取り除いたリストの取り出し
#define PP_LIST_HEAD(list) PP_ARG_HEAD list #define PP_LIST_TAIL(list) (PP_ARG_TAIL list) PP_LIST_HEAD((a, b, c)) // => a PP_LIST_TAIL((a, b, c)) // => (b, c)
N番目の要素の取り出し
#define PP_LIST_GET(list, n) PP_CAT(PP_ARG_GET_, n) list PP_LIST_GET((a, b, c), 0) // => a PP_LIST_GET((a, b, c), 1) // => b PP_LIST_GET((a, b, c), 2) // => c
要素の個数
#define PP_LIST_SIZE(list) PP_ARG_SIZE list PP_LIST_SIZE(()) // => 0 PP_LIST_SIZE((a)) // => 1 PP_LIST_SIZE((a, b)) // => 2 PP_LIST_SIZE((a, b, c)) // => 3
リストが空かどうか
#define PP_LIST_IS_EMPTY(list) PP_NOT(PP_LIST_SIZE(list)) PP_LIST_IS_EMPTY(()) // => 1 PP_LIST_IS_EMPTY((a)) // => 0 PP_LIST_IS_EMPTY((a, b)) // => 0 PP_LIST_IS_EMPTY((a, b, c)) // => 0
要素の追加
#define PP_LIST_PUSH_FRONT(list, item) PP_IF(PP_LIST_SIZE(list), (item, PP_LIST_UNPACK(list)), (item)) #define PP_LIST_PUSH_BACK(list, item) PP_IF(PP_LIST_SIZE(list), (PP_LIST_UNPACK(list), item), (item)) PP_LIST_PUSH_FRONT((), a) // => (a) PP_LIST_PUSH_FRONT((b), a) // => (a, b) PP_LIST_PUSH_FRONT((b, c), a) // => (a, b, c) PP_LIST_PUSH_BACK((), a) // => (a) PP_LIST_PUSH_BACK((a), c) // => (a, b) PP_LIST_PUSH_BACK((a, b), c) // => (a, b, c)
繰り返し
#define PP_WHILE(pred, op, state) PP_WHILE_0(pred, op, state) #define PP_WHILE_0(pred, op, state) PP_IF(pred(state), PP_WHILE_1(pred, op, op(state)), state) #define PP_WHILE_1(pred, op, state) PP_IF(pred(state), PP_WHILE_2(pred, op, op(state)), state) #define PP_WHILE_2(pred, op, state) PP_IF(pred(state), PP_WHILE_3(pred, op, op(state)), state) ... #define PP_WHILE_10(pred, op, state) PP_IF(pred(state), PP_WHILE_11(pred, op, op(state)), state) #define PP_WHILE_11(pred, op, state) PP_WHILE_ERROR PP_WHILE(PP_LIST_SIZE, PP_LIST_TAIL, (a, b, c)) // => () PP_WHILE(PP_LIST_IS_EMPTY, PP_LIST_TAIL, (a, b, c)) // => (a, b, c)
pred(state)
が1の間 state <- op(state)
を適用し続けます。
擬似コードで書くとこんな感じ
function PP_WHILE(pred, op, state) { while (pred(state)) state = op(state) return state }
ただしマクロ関数は再帰呼び出しが出来ないため同じマクロ関数を複数用意して順番に呼び出していくことで擬似的にループを実現します。
この例では10回までのイテレーションが可能です。
また、PP_WHILE()
の引数pred
またはop
にPP_WHILE()
を内部で使うマクロ関数は渡せません。
その場合は再帰呼び出しになるためマクロ展開が失敗します。
そのため、PP_WHILE()
と同様のマクロ関数を複数用意しておくといいでしょう。
#define PP_WHILE_A(pred, op, state) PP_WHILE_A_0(pred, op, state) #define PP_WHILE_A_0(pred, op, state) PP_IF(pred(state), PP_WHILE_A_1(pred, op, op(state)), state) #define PP_WHILE_A_1(pred, op, state) PP_IF(pred(state), PP_WHILE_A_2(pred, op, op(state)), state) #define PP_WHILE_A_2(pred, op, state) PP_IF(pred(state), PP_WHILE_A_3(pred, op, op(state)), state) ... #define PP_WHILE_A_10(pred, op, state) PP_IF(pred(state), PP_WHILE_A_11(pred, op, op(state)), state) #define PP_WHILE_A_11(pred, op, state) PP_WHILE_ERROR #define PP_WHILE_B(pred, op, state) PP_WHILE_B_0(pred, op, state) #define PP_WHILE_B_0(pred, op, state) PP_IF(pred(state), PP_WHILE_B_1(pred, op, op(state)), state) #define PP_WHILE_B_1(pred, op, state) PP_IF(pred(state), PP_WHILE_B_2(pred, op, op(state)), state) #define PP_WHILE_B_2(pred, op, state) PP_IF(pred(state), PP_WHILE_B_3(pred, op, op(state)), state) ... #define PP_WHILE_B_10(pred, op, state) PP_IF(pred(state), PP_WHILE_B_11(pred, op, op(state)), state) #define PP_WHILE_B_11(pred, op, state) PP_WHILE_ERROR #define PP_WHILE_C(pred, op, state) PP_WHILE_C_0(pred, op, state) #define PP_WHILE_C_0(pred, op, state) PP_IF(pred(state), PP_WHILE_C_1(pred, op, op(state)), state) #define PP_WHILE_C_1(pred, op, state) PP_IF(pred(state), PP_WHILE_C_2(pred, op, op(state)), state) #define PP_WHILE_C_2(pred, op, state) PP_IF(pred(state), PP_WHILE_C_3(pred, op, op(state)), state) ... #define PP_WHILE_C_10(pred, op, state) PP_IF(pred(state), PP_WHILE_C_11(pred, op, op(state)), state) #define PP_WHILE_C_11(pred, op, state) PP_WHILE_ERROR
[追記]
BOOST_PP_AUTO_REC()
(AUTO RECursion)のような仕組みを用いることで使用すべきPP_WHILE_*
を自動的に決定できるためPP_WHILE()
の引数にPP_WHILE()
を使う関数を渡せるようにできます。
www.slideshare.net
#define PP_AUTO_REC(pred) PP_AUTO_REC_NODE_2(pred) #define PP_AUTO_REC_NODE_2(p) PP_IF(p(2), PP_AUTO_REC_NODE_1(p), PP_AUTO_REC_NODE_3(p)) # define PP_AUTO_REC_NODE_1(p) PP_IF(p(1), 1, 2) # define PP_AUTO_REC_NODE_3(p) PP_IF(p(3), 3, 4) #define PP_WHILE PP_CAT(PP_WHILE, PP_AUTO_REC(PP_WHILE_CHECK)) #define PP_WHILE_CHECK(n) PP_CAT(PP_WHILE_CHECK_, PP_WHILE ## n(PP_WHILE_CHECK_P, PP_WHILE_CHECK_O, OK)) #define PP_WHILE_CHECK_P(s) 0 #define PP_WHILE_CHECK_O(s) #define PP_WHILE_CHECK_OK 1 #define PP_WHILE1 PP_WHILE1_0 #define PP_WHILE_CHECK_PP_WHILE1_0(p, o, s) 0 #define PP_WHILE1_0(pred, op, state) PP_IF(pred(state), PP_WHILE1_1(pred, op, op(state)), state) #define PP_WHILE1_1(pred, op, state) PP_IF(pred(state), PP_WHILE1_2(pred, op, op(state)), state) ... #define PP_WHILE2 PP_WHILE2_0 #define PP_WHILE_CHECK_PP_WHILE2_0(p, o, s) 0 #define PP_WHILE2_0(pred, op, state) PP_IF(pred(state), PP_WHILE2_1(pred, op, op(state)), state) #define PP_WHILE2_1(pred, op, state) PP_IF(pred(state), PP_WHILE2_2(pred, op, op(state)), state) ... #define PP_WHILE3 PP_WHILE3_0 #define PP_WHILE_CHECK_PP_WHILE3_0(p, o, s) 0 #define PP_WHILE3_0(pred, op, state) PP_IF(pred(state), PP_WHILE3_1(pred, op, op(state)), state) #define PP_WHILE3_1(pred, op, state) PP_IF(pred(state), PP_WHILE3_2(pred, op, op(state)), state) ... #define PP_WHILE4 PP_WHILE4_0 #define PP_WHILE_CHECK_PP_WHILE4_0(p, o, s) 0 #define PP_WHILE4_0(pred, op, state) PP_IF(pred(state), PP_WHILE4_1(pred, op, op(state)), state) #define PP_WHILE4_1(pred, op, state) PP_IF(pred(state), PP_WHILE4_2(pred, op, op(state)), state) ...
算術演算
インクリメント/デクリメント
#define PP_INC(x) PP_CAT(PP_INC_, x) #define PP_INC_0 1 #define PP_INC_1 2 #define PP_INC_2 3 ... #define PP_INC_9 10 #define PP_INC_10 10 #define PP_DEC(x) PP_CAT(PP_DEC_, x) #define PP_DEC_0 0 #define PP_DEC_1 0 #define PP_DEC_2 1 ... #define PP_DEC_9 8 #define PP_DEC_10 9 PP_INC(0) // => 1 PP_INC(1) // => 2 PP_INC(2) // => 3 PP_INC(10) // => 10 PP_DEC(0) // => 0 PP_DEC(1) // => 0 PP_DEC(2) // => 1 PP_DEC(10) // => 9
結果の値が[0, N]の範囲にクランプされてたほうが扱いやすいのでPP_INC(10) == 10
, PP_DEC(0) == 0
になるようにしています。
加算
PP_WHILE()
とPP_INC()
, PP_DEC()
を用いて繰り返し数値をインクリメントすることで実現します。
#define PP_ADD(x, y) PP_LIST_GET(PP_WHILE(PP_LIST_HEAD, PP_ADD_O, (x, y)), 1) #define PP_ADD_O(xy) (PP_DEC(PP_LIST_GET(xy, 0)), PP_INC(PP_LIST_GET(xy, 1))) PP_ADD(2, 3) // => 5 PP_ADD(5, 2) // => 7
結果は[0, 10]の範囲に丸められます。
PP_WHILE()
を内部で使用するため、別のPP_WHILE_*
を使う同様の関数を用意しておくといいです。
#define PP_ADD_A(x, y) PP_LIST_GET(PP_WHILE_A(PP_LIST_HEAD, PP_ADD_O, (x, y)), 1) #define PP_ADD_B(x, y) PP_LIST_GET(PP_WHILE_B(PP_LIST_HEAD, PP_ADD_O, (x, y)), 1) #define PP_ADD_C(x, y) PP_LIST_GET(PP_WHILE_C(PP_LIST_HEAD, PP_ADD_O, (x, y)), 1)
減算
#define PP_SUB(x, y) PP_LIST_GET(PP_WHILE(PP_LIST_HEAD, PP_SUB_O, (y, x)), 1) #define PP_SUB_O(xy) (PP_DEC(PP_LIST_GET(xy, 0)), PP_DEC(PP_LIST_GET(xy, 1))) PP_SUB(2, 3) // => 0 PP_SUB(5, 2) // => 3 #define PP_SUB_A(x, y) PP_LIST_GET(PP_WHILE_A(PP_LIST_HEAD, PP_SUB_O, (y, x)), 1) #define PP_SUB_B(x, y) PP_LIST_GET(PP_WHILE_B(PP_LIST_HEAD, PP_SUB_O, (y, x)), 1) #define PP_SUB_C(x, y) PP_LIST_GET(PP_WHILE_C(PP_LIST_HEAD, PP_SUB_O, (y, x)), 1)
結果は[0, 10]の範囲に丸められます。
同様に繰り返し加減算を行うことで乗算や除算、剰余なども実装できますが割愛します。
さて、これで条件分岐、繰り返しと加減算が扱えるようになったので色々なものが書けます。
(0, 1, 2, ..., n-1)
のようなリストを生成したり、
#define PP_IOTA(n) PP_LIST_GET(PP_WHILE(PP_LIST_HEAD, PP_IOTA_O, (n, ())), 1) #define PP_IOTA_O(nl) (PP_DEC(PP_LIST_GET(nl, 0)), PP_LIST_PUSH_FRONT(PP_LIST_GET(nl, 1), PP_DEC(PP_LIST_GET(nl, 0)))) PP_IOTA(0) // => () PP_IOTA(3) // => (0, 1, 2) PP_IOTA(10) // => (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
リストを逆順にしたり、
#define PP_REVERSE(list) PP_LIST_GET(PP_WHILE(PP_REVERSE_P, PP_REVERSE_O, (list, ())), 1) #define PP_REVERSE_P(io) PP_LIST_SIZE(PP_LIST_GET(io, 0)) #define PP_REVERSE_O(io) (PP_LIST_TAIL(PP_LIST_GET(io, 0)), PP_LIST_PUSH_FRONT(PP_LIST_GET(io, 1), PP_LIST_HEAD(PP_LIST_GET(io, 0)))) PP_REVERSE(PP_IOTA(5)) // => (4, 3, 2, 1, 0)
リストの各要素に関数を適用したり。
#define PP_MAP(list, f) PP_LIST_GET(PP_WHILE(PP_MAP_P, PP_MAP_O, (list, f, ())), 2) #define PP_MAP_P(ifo) PP_LIST_SIZE(PP_LIST_GET(ifo, 0)) #define PP_MAP_O(ifo) PP_MAP_O_I ifo #define PP_MAP_O_I(in, f, out) (PP_LIST_TAIL(in), f, PP_LIST_PUSH_BACK(out, f(PP_LIST_HEAD(in)))) #define DOUBLE(x) PP_ADD_A(x, x) PP_MAP(PP_IOTA(4), DOUBLE) // => (0, 2, 4, 6, 8) #define ADD_PREFIX_n(x) PP_CAT(n, x) int PP_LIST_UNPACK(PP_MAP(PP_IOTA(3), ADD_PREFIX_n)); // => int n0, n1, n2;
最後の例とかC++03書く時とか便利だったりするんじゃないでしょうか。
まとめ
以上全コード
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ
プリプロセッサでも様々なことが出来るのがわかったと思います。
手触りとしては再帰呼び出しと算術演算の出来ないSchemeって感じです。
試してみると結構楽しいのでおすすめです(?)
コードはまだ洗練できていませんがFizz Buzzとクイックソートを実装したので供養しておきます。
プリプロセッサFizz Buzzです
— りょがまや (@Ryooooooga) 2018年2月2日
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ https://t.co/buOIonMtXZ pic.twitter.com/0gqmwVqKL9
プリプロセッサ時クイックソートです(Wandboxだとメモリ喰い付くして死にました)
— りょがまや (@Ryooooooga) 2018年2月3日
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ https://t.co/lxs1d3Z95I pic.twitter.com/ImYgWn5s98
疑似再帰一段減らしたら動いた
— りょがまや (@Ryooooooga) 2018年2月3日
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ https://t.co/JLvcY2WGnu