SlideShare a Scribd company logo
Define and Expansion of CPP Macro
Cプリプロセッサマクロの定義と展開
2014/06/28
ドワンゴC++勉強会 #1
でちまる
自己紹介
● でちまる
● @decimalbloat
● 仕事
– 艦これコンテンツの回収と観賞
– ツイッター上で見かけた不思議C++コードを見て実装を
看破する
– Android上でJavaを書く(web系ではない)
本日の発表は
● constexpr
● Cプリプロセッサ
● C++老害トーク
● どれも明日から職場で即実践できる話ばかり
● 是非ご活用ください
今日の話
● Cプリプロセッサメタプログラミングについて
● Cプリプロセッサメタプログラミングの技法
Cプリプロセッサメタプログラミング
について
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
– 即席RAIIのためのマクロ
– 簡単にいうと次のようなもの
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 << "hogen"; };
SCOPE_EXIT { std::cout << "fuga, "; };
}
→ fuga, hoge
Scope Exit の簡単な実装
● RAIIで必要な処理をさせるためにはそれ用の変数
もしくはクラスに名前を付ける必要がある
● しかし単にデストラクタを呼ぶだけなので,人間側
の都合としてはどうでもよい
●
そんなわけでこういうマクロを書けば余計な名前を
考える手間もコード上のノイズも減る
よく言われる問題
野蛮
● 何事も暴力で解決するのが一番だ
――NINJASLAYER より レッドゴリラ=サン
● CプリプロセッサはC++の構文も意味論も理解しな
い
● まーでもこれ以外ないから暴力で解決だ
では構文を解すればよいのでは?
● ついこの間提案された
● http://www.open-
std.org/jtc1/sc22/wg21/docs/papers/2014/n3883
.html
● 10年単位で待てばいけるかもしれない
名前が衝突する
● BOOST_PP_ とかそういうprefix付ければ普通衝
突しない
● それでも衝突するのはただの当たり屋では?
コンパイルエラーがひどい
● はい
● ただしそれは関数マクロでやった場合の話
● include主体でやればそうでもない(後述しようと
思ったが時間がなくなった)
制限が多い
● はい
● 再帰展開できないとかそういうのがある
● そもそも,アプリケーション開発でコード生成が必
要になったのなら別にCプリプロセッサでなくPHPと
かPerlとかでよい
● わざわざCプリプロセッサでやるのは,PHPがない
環境でもコード生成したいから
ここまでのまとめ
● PHP使えよ
Cプリプロセッサメタプログラミング
の技法
選べる2つのスタイル
● 関数マクロ濫用スタイル
● include濫用スタイル(こっちの話はしない)
関数マクロ濫用スタイルにおける基本
的な技法
●
基本
– 連結と再展開
– 評価順の制御
– 引数の解体
● 応用
– 数値と真理値
– 分岐
– 繰り返し
– データ構造
基本
連結と再展開
● #define CAT(a, b) a ## b
#define AB C
CAT(A, B)
→C
● 連結してできたトークンをマクロ名として定義してお
くことで,更に展開する
評価順の制御
● #define CAT(a, b) a ## b
CAT(A, CAT(B, C))
ACAT(B, C)
● マクロの置換規則はC++の関数とは大きく違うの
でなかなか分かりづらい
評価順の制御
● 置換の基本は
– 外側のマクロを置き換えてから,
– トークン連結して,
– それぞれの引数を個別に置換して,
– 元の場所に挿入して,
– その場所からもう一度置換する
評価順の制御
● #define CAT(a, b) a ## b
CAT(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) f
IF(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
分岐
● IFと組み合わせればCのif風のものもできる
● #define IF2(n, t, f) IF(BOOL(n), t, f)
数値計算
● とりあえず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 ## n
REPEAT(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
データ構造
● いくつかあるがよく使われているのは次の二つ
– (a)(b)(c)
– (a, b, c)
Sequence
● (a)(b)(c) というような形式を sequenceと言う
● 先頭または末尾への要素の追加,削除はO(1)で
できるが要素へのアクセスはO(N)なデータ構造
Sequence
● コード例はオンラインで
Tuple
● (a, b, c) のような形式をtupleという
● 長さを変更できないが要素へのアクセスはO(1)な
データ構造
– だった
● C++11からVariadic Macroが導入されたので長さ
の制限がなくなった
Tuple
● コード例はオンラインで
データ構造
● だいたいこの2つがあればまともなプログラムが書
ける
● いずれのデータ構造用の関数マクロも,ループ同
様職人が丹精こめて書き上げたコピペ手直しコー
ドでできている
まとめ
● PHPを使え
● 置換とトークン連結だけでも足し算は作れる
● たとえ貧弱な機能でも人間は濫用する.
抵抗は無意味だ
参考になるソースコード
● http://boost.org/
● https://github.com/dechimal/desalt
質疑応答
終わり

More Related Content

Define and expansion of cpp macro