マクロ関数の引数から括弧を除く
【環境】Visual Studio.NET 2003/ WindowsXP Pro.
マクロ関数はプリプロセッサメタプログラミングをするにおいて欠かせないものですがその名の通りコンパイルプロセス前に処理を行うため困る場面もあります。次の例を見てみます。
#define SOME_MACRO( x ) /.../ /.../ SOME_MACRO( Function( i, j ) ) SOME_MACRO( TemplateClass< Type1, Type2 > ) //C4002
SOME_MACROという1引数のマクロ関数を定義し、3行目でそこに関数呼び出しを引数として与えました。丸括弧( '(', ')' )内は1つのトークンとして扱われるため括弧内のコンマはデリミタとして見なされずこれはOKです。一方5行目ではテンプレートクラスを引数として与えていますが、山括弧( '<', '>' )の場合丸括弧のような扱いは受けないためそのまま素通りされてしまいます。すると与えられた引数は"TemplateClass< Type1"と" Type2 >"という2引数と見なされてしまうためC4002コンパイルエラーが発生します。
これに対する対処方として一般的には引数を括弧で囲む方法があります。
SOME_MACRO( (TemplateClass< Type1, Type2 >) ) //OK
これでマクロ関数への引数としての受け渡しは出来ますが、問題は依然として残ります。渡すものがテンプレート関数オブジェクトの関数呼び出し文などそれ単独で式となるものであれば良いのですが上記の様に型名そのものであった場合、そのままでは使いようがなく括弧を外す必要があります。
では、どうやって括弧を外すのでしょうか。丸括弧はマクロ関数の一部であることを考えれば次の方法があります。
#define REMOVE_PAREN( x ) REMOVE_PAREN_I ## x #define REMOVE_PAREN_I(a, b) a, b
REMOVE_PARENに括弧付きの何か、例えば(arg1, arg2)を渡した場合、それはREMOVE_PAREN_Iの後ろに直接トークン結合されREMOVE_PAREN_I(arg1, arg2)となります。すると2引数を受け取るREMOVE_PAREN_Iの定義があるためそれにしたがいarg1, arg2となり無事括弧が外れます。
しかしながらこれにも問題はあって、見ての通り2引数にしか対応していません。0からn引数まで定義を用意すれば事実上どんな括弧でも外せますがそのためには引数の数を明示しなくてはならないという問題があり汎用性に欠けます*1。とは言っても他に方法が無いため引数数の明示によって括弧を外します。これをライブラリ化したものがBoost.Preprocessorにあり、それらのマクロ関数は"boost/preprocessor/tuple/rem.hpp"で定義されています。引数の数が分かっているようなちょっとした用途であれば上記のように自作してもよいのですが汎用プログラミングをする場合はこれを利用すると便利です。
*1:1引数なら括弧無しで渡しそのまま展開、2引数以上なら括弧を外して展開といった自動選択ができれば便利なのですが。