C言語への感謝の正拳突き 1日目です。
#概要
タイトルは「マクロによる似非lambda」とか偉そうなことを言ってますが、
要はマクロを使って重複した宣言や記述を取り除ためのtipsの紹介です。
冗長な関数定義、switchのcase定義、structの定義
などにおける繰り返しを除去し、マクロを使って一ヶ所に纏めることができます。
似非lambdaは3ステップから構成されています。
(1) 繰り返し要素のリストを定義するマクロ
(2) 手続きを定義するマクロ
(3) リストのマクロを手続きのマクロを使って展開する
具体例
Dart SDK(ただしこいつはC++)から引用しました。
#define CORE_INTEGER_LIB_INTRINSIC_LIST(V) \ ...(1)
V(_IntegerImplementation, _addFromInteger, Integer_addFromInteger, 438687793) \
V(_IntegerImplementation, +, Integer_add, 1324179652) \
V(_IntegerImplementation, _subFromInteger, Integer_subFromInteger, 562800077)\
...
(1) CORE_INTEGER_LIB_INTRINSIC_LISTマクロによりINTRINSICの一覧を定義しています。
#define SETUP_FUNCTION(class_name, function_name, destination, fp) \ ...(2)
if (strcmp(#class_name, "::") == 0) { \
str = String::New(#function_name); \
func = lib.LookupFunctionAllowPrivate(str); \
}
// Set up all core lib functions that can be intrisified.
lib = Library::CoreLibrary();
CORE_INTEGER_LIB_INTRINSIC_LIST(SETUP_FUNCTION); ...(3)
(2) SETUP_FUNCTIONマクロにより、展開する手続きを定義しています。
SETUP_FUNCTIONの仮引数は4つありますが、CORE_INTEGER_LIB_INTRINSIC_LISTの実引数4つに対応しているのがポイントです。
(3)はCORE_INTEGER_LIB_INTRINSIC_LIST(SETUP_FUNCTION)の行です。
CORE_INTEGER_LIB_INTRINSIC_LISTで定義した要素分、
SETUP_FUNCTIONのifの式を展開することができます。
#define EMIT_CASE(class_name, function_name, enum_name, fp) \ ...(2)
case MethodRecognizer::k##enum_name: \
compiler->assembler()->Comment("Intrinsic"); \
enum_name(compiler->assembler()); \
break;
// Integer intrinsics are in the core library, but we dont want to
// intrinsify when Smi > 32 bits if we are looking for javascript integer
// overflow.
if (!(FLAG_throw_on_javascript_int_overflow && (Smi::kBits >= 32))) {
switch (function.recognized_kind()) {
CORE_INTEGER_LIB_INTRINSIC_LIST(EMIT_CASE) ...(3)
default:
break;
}
}
こちらの例は、(2)でcase式を定義し、(3)でswitchのブロック内にcase式をリスト数分展開しています。
Dart SDKはC++ですが、ソースコードを引用したのはわかりやすい名前をつけているからです。
注意点
(1) 非常にわかりやすいシンプルな例を上げましたが、調子にのって複雑な使い方をすると痛い目にあいます。
gcc/clangのプリプロセッサでは置換できるけれども、windows clでは置換に失敗するケースに出会うことがあります。マクロの使いすぎには注意しましょう。
(2) 思ったとおりに展開されないと思ったら、プリプロセス結果を出力しておいて目視確認です。
(3) マクロで書かれた手続きの実行時デバッグは非常に困難です。プリプロセッサで1行に置換されてしまいますし、デバッガは行単位でのstep実行しかサポートしていないからです。そのためstep実行したときに止まりSEGVするのは(3)で示した行だけです。
(4) マクロを使った小手先の共通化や抽象化より、C++のテンプレートのほうが良い場合が多いため、ModernなC++の使用を検討しましょう。
このマクロ置換方法の名前をご存知の方、教えていただけるとうれしいです。
以上