自己紹介
● でちまる
● @decimalbloat● 仕事
– 艦これコンテンツの回収と観賞
– ツイッター上で見かけた不思議C++コードを見て実装を看破する
– Android上でJavaを書く(web系ではない)
Cプリプロセッサメタプログラミングとは
● Cプリプロセッサで(主にC++の)コードを出力するプログラムを書く行為
● C++の構文木や意味論などは一切無視して字句レベルでプログラムを生成する
● やってることとしてはsedとかawkと変わらない
10個の連番の関数を宣言する例
● #define F(z, i, d) \ void BOOST_PP_CAT(f, i)(int);BOOST_PP_REPEAT(10, F, ~)
→ void f0(int); void f1(int); …… void f9(int);● 実際に void f0(int); … とかいうのがソースに書か
れている,と扱われる
Scope Exit の簡単な実装
#define SCOPE_EXIT \ scope_exit_t BOOST_PP_CAT(scope_exit_var, __LINE__) = [&]
struct scope_exit_t { std::function<void()> f; template<typename F> scope_exit_t(F f) : f(f) {} ~scope_exit_t() { f(); }};
int main() { SCOPE_EXIT { std::cout << "hoge\n"; }; SCOPE_EXIT { std::cout << "fuga, "; };}
→ fuga, hoge
Scope Exit の簡単な実装
● RAIIで必要な処理をさせるためにはそれ用の変数もしくはクラスに名前を付ける必要がある
● しかし単にデストラクタを呼ぶだけなので,人間側の都合としてはどうでもよい
● そんなわけでこういうマクロを書けば余計な名前を考える手間もコード上のノイズも減る
では構文を解すればよいのでは?
● ついこの間提案された
● http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3883.html
● 10年単位で待てばいけるかもしれない
制限が多い
● はい● 再帰展開できないとかそういうのがある● そもそも,アプリケーション開発でコード生成が必
要になったのなら別にCプリプロセッサでなくPHPとかPerlとかでよい
● わざわざCプリプロセッサでやるのは,PHPがない環境でもコード生成したいから
評価順の制御
● #define CAT(a, b) a ## bCAT(A, CAT(B, C))→ACAT(B, C)
● 外側のマクロを置き換えると A ## CAT(B, C)● 連結すると ACAT(B, C) なので引数としては扱わ
れなくなる
評価順の制御
● #define CAT(a, b) CAT_I(a, b)#define CAT_I(a, b) a ## bーCAT(A, CAT(B, C))
→ABC● 外側のマクロを置き換えると CAT_I(A, CAT(B, C))● トークン連結はないのでそのまま
● 引数について展開する(CATが意図通り動くとする)と CAT_I(A, BC)● 元の場所に戻してそこからもう一度置換する
● 外側のマクロを置き換えると A ## BC● 連結して ABC● 以下省略
引数の解体
● #define REM(...) __VA_ARGS__#define AP(f, x) f(42, x)#define F(n, x) F_I(n, REM x)#define F_I(n, x) F_II(n, x)#define F(n, x, y, z)AP(F, (A, B, C))
→● カッコで包めば一つの引数にできるので,任意長
のarityのマクロを固定長引数のマクロの中で扱える
● このマクロの置換がどのように行われるかは自明なので,手順は読者の課題とする(放棄)
数値と真理値
● Cプリプロセッサには数値という概念はない
● (一部の文脈を除く)
● ソースコード上に現われる1とか2とかいうトークンを数値として扱うことにする
● 同様に0と1をそれぞれtrueとfalseとして扱うことにする
● マクロの上でどうやってそれらを数値や真理値とみなすのかは次のページで
分岐
● 分岐は基本的な技法だけで書ける
● #define IF(c, t, f) CAT(IF, c)(t, f)#define IF1(t, f) t#define IF0(t, f) fIF(1, hoge, fuga)
→hoge
IF(0, hoge, fuga)
→fuga
分岐
● IFと1または0を連結することでそれが再度マクロ名となることで,二つのマクロ (IF1, IF0) を使い分ける
● C風の数値から整数への変換も次のように書ける
● #define BOOL(n) CAT(BOOL, n)#define BOOL0 0#define BOOL1 1#define BOOL2 1#define BOOL3 1…BOOL(3)
→1
数値計算
● とりあえず1加算するマクロだけ作る
● #define INC(n) INC ## n#define INC0 1#define INC1 2#define INC2 3…INC(INC(2))
→4● やってることはBOOLと同じ
繰り返し
● 最も単純な実装
● #define REPEAT(n, m) REPEAT ## n(m)#define REPEAT0(m)#define REPEAT1(m) m(0)#define REPEAT2(m) REPEAT1(m) m(1)#define REPEAT3(m) REPEAT2(m) m(2)#define F(n) hoge ## nREPEAT(3, F)→hoge0 hoge1 hoge2
● mを関数マクロとみなすことで,繰り返す内容を自由に指定できる
繰り返し
● もっと複雑なもの
● #define WHILE WHILE0#define WHILE_END(p, st, op) st#define WHILE0(p, st, op) \ IF(p(st), WHILE1, WHILE_END)(p, st, op)#define WHILE1(p, st, op) \ IF(p(op(st)), WHILE2, WHILE_END)(p, op(st), op)#define WHILE2(p, st, op) \ IF(p(op(st)), WHILE3, WHILE_END)(p, op(st), op)#define WHILE3(p, st, op) \ IF(p(op(st)), WHILE4, WHILE_END)(p, op(st), op)…
● p(st)が偽だったらst● 真だったらop(st)した結果で同じことを繰り返す
繰り返し
● 次のように使う(DECはINCの逆として定義したマクロとする)
● #define ADD(m, n) ADD_I WHILE(P, (m, n), OP)#define P(st) P_I st#define P_I(m, n) BOOL(m)#define OP(x) OP_I x#define OP_I(m, n) (DEC(m), INC(n))ADD(3, 4)
→7
注意
● 今まで見てきた繰り返しマクロは,引数のマクロ(ループ内で使うマクロ)の中で同繰り返しマクロを使うことができない
● そんなわけでこの制限を出し抜くために次のような技法が考案された
● http://www.slideshare.net/digitalghost/c-35069539
Tuple
● (a, b, c) のような形式をtupleという
● 長さを変更できないが要素へのアクセスはO(1)なデータ構造– だった
● C++11からVariadic Macroが導入されたので長さの制限がなくなった