最近、久しぶりにC言語を使った。

数百個くらいある良く似た関数を宣言するのに、C++言語のテンプレートを使ってはいけないという状況に遭遇したのでマクロで挑んでみた。
そのときに使った技を紹介する。

演算子「##」でオブジェクト指向

演算子「##」は「x ## y」のように使用し、トークン「x」にトークン「y」を連結したトークンを返す。例えば、「abc ## xyz」は「abcxyz」となる。
これを利用して、演算子「##」が返す結果が、マクロ呼出となるようにすることでオブジェクト指向っぽいことができる。

次のプログラムのマクロ「_PersonName(aThis)」は「_PersonName_」と引数で与えられた「aThis」を連結する。
例えば、「_PersonName(HarukaAmami)」は「_PersonName_ ## HarukaAmami」⇒「_PersonName_HarukaAmami」⇒「”天海春香”」というように展開される。
この展開は、インスタンス「HarukaAmami」のフィールドの値を取り出しているように見える。
複雑な例では、「_PersonDefinePrintName(aThis)」は「_PersonName(aThis)」を展開した結果を使って関数宣言を生成する。
この展開は長くなるので省略するが、インスタンス「HarukaAmami」のメソッドを呼び出しているように見える。
このように「_Person」で始まるマクロ群はクラス宣言、「HarukaAmami」のように個人名で終わるマクロ郡はインスタンス宣言のように見える。

このような工夫をすることで、「HarukaAmami」のようにな名前に複数の属性を持たせることができる。
そうすることで、マクロのパラメータに「HarukaAmami」のようにな名前を与えるだけで、それが持つ全ての属性を与えたことになる。
例えば、「_PersonDefinePrintNameAndAge(aThis)」は良い例である。このマクロの中では「aThis」を使って名前と年齢にアクセスしている。

macrotest.c
#include 

/* Personクラスマクロ */
#define _PersonName(aThis) _PersonName_ ## aThis
#define _PersonAge(aThis) _PersonName_ ## aThis
#define _PersonDefinePrintName(aThis)
void printNameFor ## aPerson (){
    printf("私の名前は%sです。", _PersonName(aThis));
}
#define _PersonDefinePrintAge(aThis)
void printAgeFor ## aPerson (){
    printf("私の年齢は%d才です。", _PersonAge(aThis));
}
#define _PersonDefinePrintNameAndAge(aThis)
void printAgeFor ## aPerson (){
    printf("私の名前は%s、年齢は%d才です。", _PersonName(aThis), _PersonAge(aThis));
}

/* Personインスタンスマクロ */
#define _PersonName_HarukaAmami "天海春香"
#define _PersonAge_HarukaAmami 16

#define _PersonName_ChihayaKisaragi "如月千早"
#define _PersonAge_ChihayaKisaragi 15

#define _PersonName_YukihoHagiwara "萩原雪歩"
#define _PersonAge_YukihoHagiwara 16

#define _PersonName_YayoiTakatsuki "高槻やよい"
#define _PersonAge_YayoiTakatsuki 13

#define _PersonName_RitsukoAkizuki "秋月律子"
#define _PersonAge_RitsukoAkizuki 18

#define _PersonName_AzusaMiura "三浦あずさ"
#define _PersonAge_AzusaMiura 20

#define _PersonName_IoriMinase "水瀬伊織"
#define _PersonAge_IoriMinase 14

#define _PersonName_MakotoKikuchi "菊池真"
#define _PersonAge_MakotoKikuchi 16

#define _PersonName_AmiFutami "双海亜美"
#define _PersonAge_AmiFutami 12

#define _PersonName_MamiFutami "双海真美"
#define _PersonAge_MamiFutami 12

#define _PersonName_MikiHoshii "星井美希"
#define _PersonAge_MikiHoshii 14

マクロを引数とするマクロ

マクロを引数とするマクロを定義することで、非常に強力な展開能力が得られる。

次の「macrotest.cの続き」のマクロ「_Spread(aMacro)」は、引数で与えられたマクロ「aMacro」にPersonクラスの全インスタンスを代入する。
これにより、11 人分の何かを宣言しなければいけない場合、このマクロを使えば簡潔に記述できる。
このプログラムでは 3 種類のマクロを「_Spread(aMacro)」に適用することで 33 個の関数宣言を生成している。

macrotest.cの続き

/* マクロを引数とするマクロ宣言 */
#define _Spread(aMacro)
aMacro(HarukaAmami    )
aMacro(ChihayaKisaragi)
aMacro(YukihoHagiwara )
aMacro(YayoiTakatsuki )
aMacro(RitsukoAkizuki )
aMacro(AzusaMiurra    )
aMacro(IoriMinase     )
aMacro(MakotoKikuchi  )
aMacro(AmiFutami      )
aMacro(MamiFutami     )
aMacro(MikiHoshii     )

/* 33 個 (11 人 × 3 種類) の関数を宣言 */
_Spread(_PersonDefinePrintName      )
_Spread(_PersonDefinePrintAge       )
_Spread(_PersonDefinePrintNameAndAge)

ちなみに、マクロがどのように展開されるのかを見たいときは VC++ なら「cl.exe /P macrotest.c」とすればよい。

カテゴリー: 技術情報

2件のコメント

squld · 2007-08-09 16:36

うわっ。変態だ!

上手く使えばコードジェネレータ作ったり、テンプレートエンジン使ったりする必要なくなるね。

より良い環境を求めて · 2008-01-12 22:59

[雑記] C言語の##マクロを使ってみた…

元ソースは http://kurusugawa.jp/blog/archives/319/ ここ。 ただし5行目の_PersonName_を_PersonAge_に、 (more…)

現在コメントは受け付けていません。