可変長マクロ引数の話

これはC++11 Advent Calendar 20114日目の記事です。
C++11ではプリプロセッサが可変長マクロ引数に対応しました。例を挙げましょう。

#define ROTATE(...) ROTATE_I(__VA_ARGS__)
#define ROTATE_I(x, ...) __VA_ARGS__, x

ROTATE(1, 2) // 2, 1
ROTATE(ROTATE(1, 2, 3)) // 3, 1, 2

というように、関数形式のマクロで任意個の引数を取ることができるようになる機能です。
それではこの新機能について詳しく見ていきましょう。
(なお、ここではプリプロセッサの話しかしないので、今後はいちいち「関数形式のマクロ」と言わず単に「関数」と言うことにします。また同様に、「識別子」と言えば「プリプロセス時の識別子」のことです。)

まずは関数の定義です。

#define ROTATE(...) ROTATE_I(__VA_ARGS__)
#define ROTATE_I(x, ...) __VA_ARGS__, x

"..." が可変長なパラメータリストの部分を表します。対して、"..." で捕えた引数リストは__VA_ARGS__という識別子で利用します。ROTATEにおいては全ての引数が__VA_ARGS__によって捕捉されますが、ROTATE_Iでは最初の引数だけxに捕捉され、残りの引数が __VA_ARGS__ に捕捉されます。
この識別子は、単一の引数ではなく引数リストを捕捉するという性質以外に、その他の関数のパラメータとの違いはありません。これが意味するところはまたの機会ということにして、今はとりあえず「F(__VA_ARGS__)とか書いたらちゃんと__VA_ARGS__内に含まれるカンマは引数の区切りとして認識される」程度に思っておいてください。
ところで、上のROTATEの実装では、一部望ましくない挙動を示します。次のコード

ROTATE(1)

はエラーとなります。これはROTATE_Iが原因で、

#define F(x, ...) …

なる関数Fにおいて、

F(a)

という呼び出しが規格では許されておらず、空でもいいのでxの後にもう一つ引数を要求します。つまり、少なくともF(x,)みたいに書く必要があります。1パラメータ + "..."の場合に限らず、一般にn>=1パラメータ + "..."で定義された関数に対してn引数での呼び出しは許されていません。個人的には可変長テンプレート引数の挙動と合わせてほしいのですがまぁそうなっていないものはなっていないので仕方ありません(ただしGCCとClangでは、-pedantic-errorsを付けない限りnパラメータ + "..." に対するn引数での呼び出しを許可します)。
であるため、ROTATEについてのより好ましい実装は次のようになります。

#include "boost/preprocessor/variadic/size.hpp"
#include "boost/preprocessor/comparison/equal.hpp"
#include "boost/preprocessor/control/iif.hpp"
#include "boost/preprocessor/tuple/rem.hpp"

#define ROTATE(...) BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), BOOST_PP_TUPLE_REM(), ROTATE_I)(__VA_ARGS__)
#define ROTATE_I(x, ...) __VA_ARGS__, x
ROTATE(1)
ROTATE(1, 2)
ROTATE(ROTATE(1, 2, 3))

Boost.Preprocessorを利用して、引数の数が1つである場合だけ、何もしないようにする、という判定を入れました。もしかしたら「0引数の場合は?」と思われる方もいらっしゃるかもしれませんが、その場合__VA_ARGS__は空のトークン列を捕捉しているので結局1引数と同様に処理されます。

いかがでしたでしょうか?今まで以上に便利になったカンマ区切りトークン列でより快適なプログラミングが可能になるでしょう。

また、カンマ区切りのトークン列というのはC++プログラマにとって大変なじみ深い見た目であるため、これを自由に使えるようになるということは、よりC++になじんだ見た目の擬似的拡張構文を作る際にも役立ちます。

それでは次のC++ Advent Calendar 2011, id:bolerosをお楽しみください(遅いわボケが)。