Boost.Preprocessor でプログラミングしましょう DigitalGhost http://d.hatena.ne.jp/digitalghost/ http://twitter.com/decimalbloat
私のこと hatena のプロフィールとか 見てください
とりあえず FizzBuzz 書いてみた #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t) BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _) include は省略してます
gcc -P で展開 1, 2, FIZZ, 4, BUZZ, FIZZ, 7, 8, FIZZ, BUZZ, 11, FIZZ, 13, 14, FIZZBUZZ, 16, 17, FIZZ, 19, BUZZ コンパイルすらせず 解けた!
Boost.Preprocessor について コピペ ちょっとだけ変更 を人間が繰り返す代わりに プリプロセッサで自動化するためのマクロいろいろ template<typename T1, typename T2,, typename T50> とかいうテンプレート ( 実際にBoostにはあります ) を作るときとかにとても便利
使われている例 Boost.ScopeExit Boost.Typeof Boost.ConceptCheck Boost.Parameters etc...
実行環境 VC : cl.exe /EP ソースファイル gcc : cpp -P ソースファイル cl.exe /EP だと プリプロセスディレクティブの行も空行として残ってしまうので 適当に削除してください for /f "delims=" %i in ('cl.exe /EP pp.cpp') do @if not "%i"=="" @echo %i
Hello World! in Preprocessor ソース hello.cpp Hello, World! $ gcc -P hello.cpp Hello, World!
Hello World! in Preprocessor ソース hello2.cpp #define HELLO(x) Hello, x! HELLO(x) $ gcc -P hello2.cpp Hello, World!
FizzBuzz はどうなってるの? #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t) BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _)
BOOST_PP_ENUM_SHIFTED(n, op, data) 展開する数とマクロで 1 (n 1) まで数字 を変えながらコピペするマクロ #define DECLARE_OP(z, n, data) data ## n template< BOOST_PP_ENUM_SHIFTED(51, DECLARE_OP, typename T) > struct vector50;
BOOST_PP_ENUM_SHIFTED(n, op, data) 展開する数とマクロで 1 (n 1) まで数字 を変えながらコピペするマクロ #define DECLARE_OP(z, n, data) data ## n template< DECRARE_OP(z, 1, typename T), DECRARE_OP(z, 2, typename T), DECRARE_OP(z, 50, typename T) > struct vector50;
BOOST_PP_ENUM_SHIFTED(n, op, data) 展開する数とマクロで 1 (n 1) まで数字 を変えながらコピペするマクロ #define DECLARE_OP(z, n, data) data ## n template< typename T ## 1, typename T ## 2, typename T ## 50 > struct vector50;
BOOST_PP_ENUM_SHIFTED(n, op, data) 展開する数とマクロで 1 (n 1) まで数字 を変えながらコピペするマクロ #define DECLARE_OP(z, n, data) data ## n template< typename T1, typename T2, typename T50 > struct vector50;
関連するマクロ BOOST_PP_ENUM_SHIFTED_PARAMS(n, d) d ## 1, d ## 2, d ## (n 1) と展開する template< > BOOST_PP_ENUM_SHIFTED_PARAMS(51, typename T) struct vector50;
関連するマクロ BOOST_PP_ENUM_SHIFTED_PARAMS(n, d) d ## 1, d ## 2, d ## (n 1) と展開する template< > typename T ## 1, typename T ## 2, typename T ## 50 struct vector50;
関連するマクロ BOOST_PP_ENUM_SHIFTED_PARAMS(n, d) d ## 1, d ## 2, d ## (n 1) と展開する template< > typename T1, typename T2, typename T50 struct vector50;
関連するマクロ 関数テンプレートの場合 : BOOST_PP_SHIFTED_BINARY_PARAMS template< typename T1, typename T2, typename T50 > tuple50< T1, T2, T50 > make_tuple50( BOOST_PP_ENUM_BINARY_PARAMS(50, const T, & arg) );
関連するマクロ 関数テンプレートの場合 : BOOST_PP_SHIFTED_BINARY_PARAMS template< typename T1, typename T2, typename T50 > tuple50< T1, T2, T50 > make_tuple50( const T ## 1 & arg ## 1, const T ## 2 & arg ## 2, const T ## 50 & arg ## 50, );
関連するマクロ 関数テンプレートの場合 : BOOST_PP_SHIFTED_BINARY_PARAMS template< typename T1, typename T2, typename T50 > tuple50< T1, T2, T50 > make_tuple50( const T1 & arg1, const T2 & arg2, const T50 & arg50, ); 他にもいくつかバリエーションがあります
FizzBuzz はどうなってるの? #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t) BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _)
BOOST_PP_CAT(a, b) a と b をトークン連結します a ## b とだいたい同じです BOOST_PP_CAT(HOGE, HOGE) // HOGEHOGE
BOOST_PP_CAT(a, b) それは ## で十分じゃないの? #define BAD(a, b) a ## b #define GOOD(a, b) BOOST_PP_CAT(a, b) #define DOUBLE(a) a a BAD(HOGE, DOUBLE(FUGA)) GOOD(HOGE, DOUBLE(FUGA)) HOGEDOUBLE(FUGA) と展開されてしまう ( トークン連結のほうが先に HOGEFUGA FUGA 実行される ) になる
BOOST_PP_CAT(a, b) ちなみに実装 #define BOOST_PP_CAT(a, b) BOOST_PP_I(a, b) #define BOOST_PP_CAT_I(a, b) a ## b こうすれば a, b がマクロだった場合 全て展開が終わった後に連結します
FizzBuzz はどうなってるの? #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t) BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _)
BOOST_PP_IIF(cond, t, f) cond が 1 というトークンであれば t 0 というトークンであれば f BOOST_PP_IIF(1, HOGE, PIYO) HOGE BOOST_PP_IIF(0, HOGE, PIYO) PIYO 一つめの引数 (cond) が受け付けられるのは 0 か 1 か もしくはそう展開されるマクロのみ
プリプロセッサでの数字の扱い プリプロセッサは 1 が示す値を認識していない 1LL とか 1.0 は 値を認識して扱うのはコンパイルや実行時
プリプロセッサの数字の扱い 123 や abc を 文字の並び( トークン ) として扱っている丸カッコとカンマ以外の記号や空白が混ざっていてもトークン 123-abc;.exe も一つのトークン
関連するマクロ BOOST_PP_IF(cond, t, f) cond が 1 255 なら t 0 なら f になる BOOST_PP_EXPR_IIF(cond, t) cond が 1 なら t に 0 なら空トークンになる BOOST_PP_EXPR_IIF(1, HOGE) HOGE BOOST_PP_EXPR_IIF(0, HOGE)
FizzBuzz はどうなってるの? #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t) BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _)
BOOST_PP_BOOL(n) BOOST_PP_NOT(n) BOOST_PP_BOOL(n) n が 1 255 なら 1 0 なら 0 ( n!= 0 と同じ ) BOOST_PP_NOT(n) n が 1 255 なら 0 0 なら 1 (!n と同じ ) BOOST_PP_BOOL(42), BOOST_PP_NOT(42) 1, 0
FizzBuzz はどうなってるの? #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t) BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _)
BOOST_PP_IS_EMPTY(a) A が空トークンか 空トークンに展開されるなら 1 識別子なら 0 BOOST_IS_EMPTY(HOGE) 0 BOOST_IS_EMPTY( ) 1 #define DUMMY BOOST_IS_EMPTY(DUMMY) 1 (2 番目の例は VC では警告がでます )
FizzBuzz はどうなってるの? #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t) BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _)
BOOST_PP_MOD(m, n) a を b で割った余り ( つまり m % n) BOOST_PP_MOD(5, 3) 2 BOOST_PP_MOD(BOOST_PP_MOD(139, 25), 8) 6 BOOST_PP_ADD BOOST_PP_SUB BOOST_PP_MUL BOOST_PP_DIV もあります
もう一度 数字に関する注意 BOOST_PP_IIF と同じく C++ の値を認識しているわけではないので BOOST_PP_ADD(1 + 1, 2 + 2) とかはできない BOOST_PP_ADD( BOOST_PP_ADD(1, 1), BOOST_PP_ADD(2, 2)) これは OK
FizzBuzz はどうなってるの? #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t) BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _)
FizzBuzz を手動で展開 BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _) を展開 FIZZBUZZ_OP(z, 1, _), FIZZBUZZ_OP(z, 2, _), FIZZBUZZ_OP(z, 100, _) FIZZBUZZ_OP は 1 1 2 2 3 FIZZ というようなことをする
FizzBuzz を手動で展開 FIZZBUZZ_OP を C++ で書いてみる string fizzbuzz_op(int n) { } string const & fizzbuzz = string(!(n % 3)? "FIZZ" : "") + string(!(n % 5)? "BUZZ" : ""); return fizzbuzz.empty()? lexical_cast<string>(n) : fizzbuzz; (C++ 的にはすごく効率が悪いですが 後の解説のためなので見逃してください )
FizzBuzz を手動で展開 この関数をこんな風に置き換え str1 + str2 BOOST_PP_CAT(str1, str2) m % n BOOST_PP_MOD(m, n)!n BOOST_PP_NOT(n) str.empty() BOOST_PP_IS_EMPTY(str) c? "str" : "" BOOST_PP_EXPR_IIF(c, str) c? a : b BOOST_PP_IIF(c, a, b)
FizzBuzz を手動で展開 ここからプリプロセッサで置き換え string fizzbuzz_op(int n) { string const & fizzbuzz = string(!(n % 3)? "FIZZ" : "") + string(!(n % 5)? "BUZZ" : ""); return fizzbuzz.empty()? lexical_cast<string>(n) : fizzbuzz; }
FizzBuzz を手動で展開 str1 + str2 BOOST_PP_CAT(str1, str2) string fizzbuzz_op(int n) { string const & fizzbuzz = BOOST_PP_CAT(!(n % 3)? "FIZZ" : "",!(n % 5)? "BUZZ" : ""); return fizzbuzz.empty()? lexical_cast<string>(n) : fizzbuzz; }
FizzBuzz を手動で展開 c? "str" : "" BOOST_PP_EXPR_IIF(c, str) string fizzbuzz_op(int n) { string const & fizzbuzz = BOOST_PP_CAT( BOOST_PP_EXPR_IIF(!(n % 3), FIZZ), BOOST_PP_EXPR_IIF(!(n % 5), BUZZ)); return fizzbuzz.empty()? lexical_cast<string>(n) : fizzbuzz; }
FizzBuzz を手動で展開!n BOOST_PP_NOT(n) string fizzbuzz_op(int n) { string const & fizzbuzz = BOOST_PP_CAT( BOOST_PP_EXPR_IIF(BOOST_PP_NOT(n % 3), FIZZ), BOOST_PP_EXPR_IIF(BOOST_PP_NOT(n % 5), BUZZ)); return fizzbuzz.empty()? lexical_cast<string>(n) : fizzbuzz; }
FizzBuzz を手動で展開 m % n BOOST_PP_MOD(m, n) string fizzbuzz_op(int n) { string const & fizzbuzz = BOOST_PP_CAT( BOOST_PP_EXPR_IIF(BOOST_PP_NOT( BOOST_PP_MOD(n, 3)), FIZZ), BOOST_PP_EXPR_IIF(BOOST_PP_NOT( BOOST_PP_MOD(n, 5)), BUZZ)); return fizzbuzz.empty()? lexical_cast<string>(n) : fizzbuzz; }
FizzBuzz を手動で展開 FIZZBUZZ_OP_II を定義 string fizzbuzz_op(int n) { string const & fizzbuzz = BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), FIZZBUZZ_OP_II(n, 5, BUZZ)); return fizzbuzz.empty()? lexical_cast<string>(n) : fizzbuzz; } #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t)
FizzBuzz を手動で展開 c? a : b BOOST_PP_IIF(c, a, b) string fizzbuzz_op(int n) { string const & fizzbuzz = BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), FIZZBUZZ_OP_II(n, 5, BUZZ)); return BOOST_PP_IIF(fizzbuzz.empty(), n, fizzbuzz); } #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t)
FizzBuzz を手動で展開 str.empty() BOOST_PP_IS_EMPTY(str) string fizzbuzz_op(int n) { string const & fizzbuzz = BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), FIZZBUZZ_OP_II(n, 5, BUZZ)); return BOOST_PP_IIF(BOOST_PP_IS_EMPTY(fizzbuzz), n, fizzbuzz); } #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t)
FizzBuzz を手動で展開 fizzbuzz_op をマクロ化 #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t)
FizzBuzz を手動で展開 完成! #define FIZZBUZZ_OP(z, n, d) \ FIZZBUZZ_OP_I( \ BOOST_PP_CAT(FIZZBUZZ_OP_II(n, 3, FIZZ), \ FIZZBUZZ_OP_II(n, 5, BUZZ)), \ n) #define FIZZBUZZ_OP_I(t, n) \ BOOST_PP_IIF(BOOST_PP_IS_EMPTY(t), n, t) #define FIZZBUZZ_OP_II(m, n, t) \ BOOST_PP_EXPR_IIF(BOOST_PP_NOT(BOOST_PP_MOD(m, n)), t) BOOST_PP_ENUM_SHIFTED(101, FIZZBUZZ_OP, _)
もっと複雑な例 make_smart newでt 型の値を作ってすぐにスマートポインタで管理する場合 だいたい smart_ptr<t>(new T(param1, param2 )); という感じに書くこれは面倒だし 生のポインタが一瞬登場してしまう 完全にポインタを消したい関数を作ってラップしてしまおう
make_smart_ptr(1 引数版 ) make_smart(1 引数版 ) template<typename T, typename T0> my_smart_ptr<t> make_smart(t0 param0) { return my_smart_ptr<t>(new T(param0)); } これでは値で引数を受けているので効率が悪い参照を使おう
make_smart_ptr(1 引数版 ) make_smart(1 引数版 ) template<typename T, typename T0> my_smart_ptr<t> make_smart(t0 & param0) { return my_smart_ptr<t>(new T(param0)); } これでもまだ const 参照が使えないので不便
make_smart_ptr(1 引数版 ) make_smart(1 引数版 ) template<typename T, typename T0> my_smart_ptr<t> make_smart(t0 & param0) { return my_smart_ptr<t>(new T(param0)); } template<typename T, typename T0> my_smart_ptr<t> make_smart(t0 const & param0) { return my_smart_ptr<t>(new T(param0)); } これで 引数が const 参照かそうでないかによって呼び分けられる
make_smart_ptr(2 引数版 ) 1つ目の引数が const / 非 const 2つ目の引数が const / 非 const で 2 * 2 = 4 パターン必要 template<typename T, typename T0, typename T1> my_smart_ptr<t> make_smart(t0 & param0, T1 & param1) { return my_smart_ptr<t>(new T(param0, param1)); } template<typename T, typename T0, typename T1> my_smart_ptr<t> make_smart(t0 const & param0, T1 & param1) { return my_smart_ptr<t>(new T(param0, param1)); } template<typename T, typename T0, typename T1> my_smart_ptr<t> make_smart(t0 & param0, T1 const & param1) { return my_smart_ptr<t>(new T(param0, param1)); } template<typename T, typename T0, typename T1> my_smart_ptr<t> make_smart(t0 const & param0, T1 const & param1) { return my_smart_ptr<t>(new T(param0, param1)); }
make_smart_ptr(n 引数版 ) 3 要素タプル版 1 つ目の引数が ( 略 ) で 2 * 2 * 2 = 8 パターン 4 要素 ( 略 ) 1 つ目 ( 略 ) で 2 *( 略 )= 16 パターン 結局ここまでだけでも 2 + 4 + 8 + 16 = 30 パターン書かないといけない 面倒!
Boost.PP で自動生成 #define DEF_MAKE_SMART_OVERLOADS_OP(z, n, data) \ BOOST_PP_SEQ_FOR_EACH_PRODUCT(DEF_MAKE_TUPLE, ((n)) BOOST_PP_REPEAT(n, MAKE_CONST_SEQ, _)) #define DEF_MAKE_SMART(r, seq) \ DEF_MAKE_SMART_I(BOOST_PP_SEQ_HEAD(seq), BOOST_PP_SEQ_TAIL(seq)) #define DEF_MAKE_SMART_I(n, seq) \ template< typename T, BOOST_PP_ENUM_PARAMS(n, typename T) > \ my_smart_ptr<t> \ make_smart(boost_pp_for((n, 0, seq), PARAMS_P, PARAMS_OP, DECL_PARAMS)) { \ return my_smart_ptr<t>(new T(BOOST_PP_ENUM_PARAMS(n, param))); \ } #define PARAMS_P(r, state) PARAMS_P_I state #define PARAMS_P_I(n, i, seq) BOOST_PP_GREATER(n, i) #define PARAMS_OP(r, state) PARAMS_OP_I state #define PARAMS_OP_I(n, i, seq) \ (n, BOOST_PP_INC(i), BOOST_PP_SEQ_TAIL(seq)) #define DECL_PARAMS(r, state) DECL_PARAMS_I state #define DECL_PARAMS_I(n, i, seq) \ BOOST_PP_COMMA_IF(i) T ## i BOOST_PP_SEQ_HEAD(seq) & param ## i #define MAKE_CONST_SEQ(z, n, _) (()(const)) BOOST_PP_REPEAT_FROM_TO(1, 6, DEF_MAKE_SMART_OVERLOADS_OP, _)
代入 #define X() 4 #define Y() X() #undef X Y Y は X() と展開される 4 と展開したい!
そこで #define X() 4 #define BOOST_PP_VALUE 1 + 2 + 3 + X() #include BOOST_PP_ASSIGN_SLOT(1) #undef X BOOST_PP_SLOT(1) BOOST_PP_SLOT(1) は 10 に展開される 素敵!
誰得? Boost.Typeof は 型を整数の列にエンコード する必要があるので 型に一意な整数 ID を振る ために使われています
他に BOOST_PP_ITERATE 自分自身を繰り返し #include します BOOST_PP_ENUM で書くにはマクロが大きすぎる場合に便利
Boost.PP を読むときの注意 BOOST_PP_AUTO_REC というマクロが登場しますが これが曲者です http://d.hatena.ne.jp/digitalghost/20090903/1252002035 に概要が あります
終わり