48
Define and Expansion of CPP Macro Cプリプロセッサマクロの定義と展開 2014/06/28 ドワンゴC++勉強会 #1 でちまる

Define and expansion of cpp macro

Embed Size (px)

Citation preview

Define and Expansion of CPP MacroCプリプロセッサマクロの定義と展開

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 << "hoge\n"; }; 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 CCAT(A, B)

→C● 連結してできたトークンをマクロ名として定義してお

くことで,更に展開する

評価順の制御

● #define CAT(a, b) a ## bCAT(A, CAT(B, C))ACAT(B, C)

● マクロの置換規則はC++の関数とは大きく違うのでなかなか分かりづらい

評価順の制御

● 置換の基本は– 外側のマクロを置き換えてから,

– トークン連結して,

– それぞれの引数を個別に置換して,

– 元の場所に挿入して,

– その場所からもう一度置換する

評価順の制御

● #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

分岐

● 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 ## 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

データ構造

● いくつかあるがよく使われているのは次の二つ

– (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

質疑応答

終わり