君はまだ,本当のプリプロセスを知らない
C Preprocessor's Counterattack
あいさつ
● でちまる (@decimalbloat)
● http://libdechimal.so/
● http://github.com/dechimal/desalt/
● 仕事募集中
● 本日の誰得発表第2部
今日の話を三行で
● ある型と振る舞いが同じな別の型が欲しい
● できるだけ簡単に生成するマクロ書いたよ
● みなさんと一緒にそのコードを探
検することで,Cプリプロセッサ
の深淵を共有したい
目次
● Strong Typedef とは
● Desalt.Newtype とは
● Desalt.Newtype の内部(今日の主題)
Strong Typedef とは
よくある事例
● “A:” >> qi::int_ % ','
| “B:” >> qi::int_ % ','
| “C:” >> qi::string
● 結果は variant<vector<int>, string> 相当
● しかし A: と B: の場合で区別したい
● variant<vector<int>, vector<int>,
string> になりませんか?
よくある事例
● struct player {
unsigned hp;
unsigned atk;
void attack(player & target) {
target.hp -= this->hp;
}
};
_人人人人人人_
> 体力勝負 <
 ̄Y^Y^Y^Y^Y ̄
解決法
● 単純に元の型の値をメンバに持つだけのラッパを書く
– おもんない
– 取り出したあとは同じ型なので後者の例だとやはり取り違える
可能性がある
● 元の型と同じI/Fと振る舞いを持つ型で代用する
– その型を作るのが果てしなく面倒
– Strong Typedef を使えば楽
– 既存の実装は整数限定だし汎用的に使えるの作ろう
Desalt.Newtype とは
Desalt.Newtype
● https://github.com/dechimal/desalt/blob/master/te
st/newtype.cpp
● Strong Typedef の一種
● Boost.Serialization のものとは違って整数以外に適用で
きる
お詫び
● 今 github に上がってるコードは gcc のバージョン上げたら
動かなくなってました☆(・ω<)
– マクロではなく TMP の部分でエラーになってるので C++ コンパ
イラ各位にはデバッグをお願いしたく
コード例
● std::vector<int> の begin と end メンバと全ての
コンストラクタのみを公開し,as_base という名前で元
の型の値として扱える ivector クラスの定義
● DESALT_NEWTYPE(ivector, std::vector<int>,
as_base,
begin,
end,
this
);
コード例
● std::basic_string<T> の機能のうち, append と
ostream への出力だけができる mystring 型
– (append は引数も戻り値も mystring 型)
● DESALT_NEWTYPE(mystring, std::string,
as_base,
this,
auto append,
namespace explicit (operator<<)
(std::ostream &, mystring const &)
);
↑ここまで
C++ Advent Calendar 2012 への投稿 +α
今日の本題
↓ここから
Desalt.Newtype の内部
構文要素 this
● DESALT_NEWTYPE(hoge, fuga,
as_fuga,
piyo, // using fuga::piyo;
this // using fuga::fuga;
);
● this は Inheriting Ctor を使うための(このマクロに
とっての)キーワードで,つまり中で using
fuga::fuga; をしている
● どうなってるのか?
構文要素 this
● ある識別子(らしきもの)がトークン列の先頭に含
まれているかを調べればよい
● #define KEYWORDthis ,
IS_EMPTY(TUPLE_ELEM(0, (CAT(KEYWORD,
this))))
● 要するに,KEYWORD をトークン連結した相手が
this だった場合のみ空トークンになるようにして判
定している
構文要素 this
● DESALT_NEWTYPE(hoge, fuga,
as_fuga,
piyo, // using fuga::piyo;
this // using fuga::fuga;
);
● 先の方法で this 以外の未知のトークンも判定
できるので,知らないトークンだったらそのま
ま using する
構文要素 auto
● struct fuga {
fuga piyo(int, fuga const &);
};
DESALT_NEWTYPE(hoge, fuga,
as_fuga,
auto piyo // hoge piyo(int x, hoge const & y) {
); // return piyo(x, y);
// }
● auto キーワードを使うと元の関数の引数と戻り値型を新しい型で置
き換えた関数を定義する(関係ない型はそのまま)
● これをするには auto piyo から piyo を取り出す必要がある
構文要素 auto
● this の件でやった方法をそのまま流用
● #define KEYWORDauto ,
IS_EMPTY(BOOST_PP_TUPLE_ELEM(1,
(CAT(KEYWORD, auto piyo))))
● 要するに KEYWORD をくっ付けたら auto が消
えてカンマになるのでそれをタプルとして扱っ
て最初の要素(空トークン)を読み飛ばす
オンデマンド括弧
● template<typename T, typename U>
DESALT_NEWTYPE(hoge, (fuga<T, U>),
as_fuga
);
● クラステンプレートの場合だけ元のクラスの周
りに括弧が必要
● 括弧があるときだけ外す必要がある
オンデマンド括弧
● トークン列の先頭に括弧があるかどうかを調べられたらよい
● #define IS_PAREN(...) 
IS_PAREN_I(CAT(A, B __VA_ARGS__))
#define B(...) OK
#define AB 0,
#define AOK 1,
#define IS_PAREN_I(...) 
IS_PAREN_II(__VA_ARGS__)
#define IS_PAREN_II(x, ...) x
● トークン列の前にテスト用の関数マクロを置くと,展開され
たときは OK に,されなかったときはそのまま残るの
で,this のときと同じ手法で判定すればよい
空トークン
● DESALT_NEWTYPE(hoge, fuga,
as_fuga,
, // do nothing
piyo ,
);
● 何も定義しない行が存在してもよい
● ところが任意のトークン列が空かどうかを判定す
るのに Boost.PP の IS_EMPTY は使えない
空トークン
● Boost.PP の IS_EMPTY の実装
● #define IS_EMPTY(x) 
IS_EMPTY_I(x A)
#define IS_EMPTY_I(y) 
TUPLE_ELEM(2, 1, B ## y ())
#define A() , 0
#define BA() 1, 1 EMPTY
#define EMPTY()
空トークン
● この実装の問題
– カンマを扱えない
– 先頭のトークンが識別子(の一部になれる)でない
といけない
● 今の時代 __VA_ARGS__ 使えばよい
空トークン
● 先頭に何があるのか分からない場合,連結での
チェックはできない
● そこで IS_PAREN を使って実装することで連
結せずに空トークンかどうかを判定する
空トークン
● #define IS_EMPTY(...) 
IF(IS_PAREN(__VA_ARGS__ ()), 
IS_EMPTY_I, 
0 TUPLE_EAT())(__VA_ARGS__)
#define IS_EMPTY_I(...) 
IF(IS_PAREN(__VA_ARGS__), 
0 TUPLE_EAT(), 
IS_EMPTY_II)(__VA_ARGS__)
#define IS_EMPTY_II(...) 
IS_PAREN(CAT(A, __VA_ARGS__) ())
#define A() ()
空トークン
● 重要な部分はこの2つ
– IS_PAREN(__VA_ARGS__()) … A
– IS_PAREN(__VA_ARGS__) … B
● A が 1 である場合
– 空
– 括弧で始まっている
– 末尾の括弧により展開されて上のどちらかになる … α
● 上の場合で,かつ,B が 0 になる場合
– 空
– α によって A が 1 になった場合
空トークン
● A が 1 で B が 0 になるケースでは,次のどちらかになる
– 空
– 識別子っぽいもの
● IS_PAREN(CAT(A, __VA_ARGS__) ())
#define A() ()
● ここまでくるとトークン連結が使えるので,補助マクロを使っ
て,__VA_ARGS__ が空であれば () になるようにすれば判定
できる
空トークン
● 実は Desalt.Newtype では有効に使ってない
ことに気付いた
終わりに
● Cプリプロセッサを使えばまぁまぁそれっぽい
DSLを作れる
● Template だけではコピペを軽減できないとき
には迷わず使おう
● Cプリプロセッサは(まだ)友達
このマクロを書いた人は
こんなマクロを書いています
● PPLambda
– #define で定義せずにマクロを作って適用する
– http://patch-tag.com/r/digitalghost/pplambda/home
● InitWithTuple
– std::tie みたいにタプルを使って複数の変数を初期化する
– https://github.com/dechimal/init-with-tuple
● AttoTest
– 自分用ユニットテストフレームワーク
– https://github.com/dechimal/atto-test
Special Thanks (アルファベット順)
● Cryolite さん
– 本日私の隣で同じく資料を作成する傍ら励ましをいただいた
● fadis_ さん
– LibreOfficeのダウンロードを手伝っていただいた
● Flast_RO さん
– LibreOfficeのダウンロードを手伝っていただいた
● melponn さん
– 無線LAN環境を貸していただいた
● ボレロさん,昼食で遅れたみなさん
– おかげで発表資料を書く時間をいただいた

君はまだ,本当のプリプロセスを知らない