プリプロセッサでrange的なmap的なfilter的な

Boost.PPを使ってrange的なmap的なfilter的なものを実装してみました。
ぶっちゃけBOOST_PP_SEQ_TRANSFORMの再実装です。
百聞は一見に如かず。 コードを貼っておきます。雑な挙動の説明だけ下でします。
今回はプリプロセスだけでコンパイルしない系のコードの話です。
gistのコード埋め込みはなぜか時々というか頻繁に表示されないみたいなので、埋め込みコードが表示されてなかったらリロードするのがおすすめです。

gist7730050

まずはrangeです。

RANGE(0, 100)

プリプロセッサによってindex_tupleと同じような要領で[0, 100)の範囲(100は含まない事に注意)の数列

(0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) (21) (22) (23) (24) (25) (26) (27) (28) (29) (30) (31) (32) (33) (34) (35) (36) (37) (38) (39) (40) (41) (42) (43) (44) (45) (46) (47) (48) (49) (50) (51) (52) (53) (54) (55) (56) (57) (58) (59) (60) (61) (62) (63) (64) (65) (66) (67) (68) (69) (70) (71) (72) (73) (74) (75) (76) (77) (78) (79) (80) (81) (82) (83) (84) (85) (86) (87) (88) (89) (90) (91) (92) (93) (94) (95) (96) (97) (98) (99)  

に展開されるようになっています。
このように(要素)(要素)...といった文字列の並びはBoost.PPではSEQ(sequence)と呼ばれ、ライブラリ側に用意されているBOOST_PP_SEQ系の非常に使い勝手の良い各種関数マクロで簡単に色々処理出来るようなデータ構造(として扱える文字の並び)です。

MAPマクロは

MAP(IS_ODD_FILTER, RANGE(0, 100))

のようにMAP(処理関数, SEQ)という風に呼び出すことで、SEQの各要素に処理関数を適用した要素から構成されるSEQを返します。

通常の関数同様に、ネストしても期待通り内側から評価されます。
また、型を持たないので、入力するSEQの要素数と適用結果のSEQの要素数の変わるFILTER系の関数マクロ(奇数のみ残す等)も簡単に使えます。 (コンパイル時だと条件によって処理後のコンテナの要素数の減るような関数を実装するのは結構大変です)
単純な奇数のみを残すフィルターとインクリメント関数のマクロを容易して適用してみます。

#define IS_ODD_FILTER(elem) \
  BOOST_PP_IF(BOOST_PP_MOD(elem, 2), BOOST_PP_IDENTITY((elem)), BOOST_PP_EMPTY)()

#define INC(elem) (BOOST_PP_INC(elem)) // SEQにするために()でBOOST_PP_INCをラップした

MAP(INC, MAP(IS_ODD_FILTER, RANGE(0, 100)))

プリプロセッサによって、0から99までの数列のSEQが生成され、そこから奇数のみが取り出され、最後にそれらの各要素がインクリメントされたSEQが返ります。そこで、最後の要素は(100)です。

(2) (4) (6) (8) (10) (12) (14) (16) (18) (20) (22) (24) (26) (28) (30) (32) (34) (36) (38) (40) (42) (44) (46) (48) (50) (52) (54) (56) (58) (60) (62) (64) (66) (68) (70) (72) (74) (76) (78) (80) (82) (84) (86) (88) (90) (92) (94) (96) (98) (100)

次に

MAP(IS_ODD_FILTER, MAP(INC, RANGE(0, 100)))

を試すと、これは先に数列の各要素をインクリメントしてから奇数のみを残すので最後の要素は(99)ですね。

(1) (3) (5) (7) (9) (11) (13) (15) (17) (19) (21) (23) (25) (27) (29) (31) (33) (35) (37) (39) (41) (43)  (45) (47) (49) (51) (53) (55) (57) (59) (61) (63) (65) (67) (69) (71) (73) (75) (77) (79) (81) (83)  (85) (87) (89) (91) (93) (95) (97) (99)

まさか、たったこれだけのコードでプリプロセス時に実装出来るとは思いませんでした。 Boost.Preprocessorはすごい!です。


追記
余談ですが、MAP(OPERATOR, ...)の後ろの引数が可変長になっているのはRANGE(M, N)の真ん中にあるカンマを許容出来るようにする為のハックです。と思ったけれどこの例だと別にそうしなくても括弧で囲まれた状態なのでそうしなくても大丈夫でした。