Boost.Preprocessor の可変長マクロ引数対応
これはBoost Advent Calendar 2011の5日目の記事です。
さて、Boostも徐々にC++11対応が進んでいまして、その中には当然、Boost内で最も利用されている、超有名かつ超重要、某社のサーバでも利用されているという純粋関数型言語C/C++プリプロセッサ用拡張ライブラリBoost.Preprocessorも入っているわけです。そこで今回はそのBoost.PPのC++11対応についてのお話をしたいと思います。
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
というように、関数形式のマクロで任意個の引数を取ることができるようになる機能です。詳しくはC++11 Advent Calendar 2011 4で書きます(BoostではなくC++11であることに注意、この記事が最初に公開された時点ではなぜか存在しません)。完璧な流れですね。
さてここからが本題です。Boost.PPの可変長マクロ引数への対応としては、タプル操作系の関数形式マクロの機能拡張と、新しい関数形式マクロがあります(なお、ここではプリプロセッサの話しかしないので、今後はいちいち「関数形式マクロ」と言わず単に「関数」と言います)。
まずはタプル操作系関数から紹介します。
タプル操作系関数の機能拡張
機能拡張の説明の前にひとつ。普段から実務でプリプロセッサを使っていらっしゃるみなさまにそんな野暮な説明など不要であるとは思いますが、念のためごく簡単に申し上げておきますと、Boost.PPで言う「タプル」とは、バランスの取れたカッコで囲まれたカンマ区切りのトークン列です。
拡張されたタプル操作関数は、操作対象のタプルの長さを与えてやる必要がなくなりました。BOOST_PP_TUPLE_ELEMを例にとって見てみます。
// (a, b, c) の1要素目(つまりb)を取得する BOOST_PP_TUPLE_ELEM(3, 1, (a, b, c)) // 可変長マクロ引数のサポート無し BOOST_PP_TUPLE_ELEM(1, (a, b, c)) // サポート有り
サポート無し版の最初の引数が要素数です。タプルの長さを書く必要がなくなりました。なお、サポートが有る場合であっても、依然として長さを与えることもでき、その場合はサポート無しのときと同様に振る舞います。つまり長さが合っていないと実行中にエラーとなります。
なお、いくつかのタプル操作関数は「F(size) tuple」という形式を持ちますが、これらの関数では「F() tuple」という形式でサポートされます。以下、例です。
TUPLE_EAT(3) (a, b, c) TUPLE_EAT() (a, b, c)
追加された関数
実のところタプル操作系関数の拡張部分は、この追加された関数を利用して実装されています。ここでは主要な関数について説明していきます。
BOOST_PP_VARIADIC_ELEM(i, ...)
この関数は、最初の引数を除いた引数全体の展開後のトークン列から、i番目の引数を取得する関数です。ただし実装の都合上、iは64未満です。
BOOST_PP_VARIADIC_ELEM(1, a, b, c) // b #define ARGS1 a, b #define ARGS2 , c, d, e BOOST_PP_VARIADIC_ELEM(3, ARGS1 ARGS2) // d
この関数には注意があり、GCCとClangでは-pedantic-errorsを付けないと
BOOST_PP_VARIADIC_ELEM(0) BOOST_PP_VARIADIC_ELEM(1,) BOOST_PP_VARIADIC_ELEM(2,,) …
は全てvalidとなってしまいます。これは、GCCのデフォルトでは
#define F(a, ...)
なるFについて、
F(x)
を許すことに由来する挙動です(規格では許されていません)。VCやその他のコンパイラのプリプロセッサではどうなのか知りませんが、ご存知の方がいれば教えてください。
なお、これはその他の可変長マクロ引数に対応している関数全体についても言えます。
BOOST_PP_VARIADIC_SIZE(...)
...を全て展開した後のトークン列に対して、カンマ区切りの要素数を数える関数です。
BOOST_PP_VARIADIC_SIZE(a, b, c) // 3 BOOST_PP_VARIADIC_SIZE(,,,,) // 5
この関数にも注意があり、
BOOST_PP_VARIADIC_SIZE()
は1です。これについてはC++11 Advent Calendar 4を参照してください。
BOOST_PP_VARIADIC_TO_*(...)
以下の4つの関数
- BOOST_PP_VARIADIC_TO_ARRAY
- BOOST_PP_VARIADIC_TO_LIST
- BOOST_PP_VARIADIC_TO_SEQ
- BOOST_PP_VARIADIC_TO_TUPLE
は、...を展開してできたカンマ区切りトークン列を、名前にある通りのデータ構造を持つトークン列に変換する関数です。釈迦に説法ではございますが念のために例を挙げますと、
BOOST_PP_VARIADIC_TO_ARRAY(a, b, c) // (3, (a, b, c)) BOOST_PP_VARIADIC_TO_LIST(a, b, c) // (a, (b, (c, BOOST_PP_NIL))) BOOST_PP_VARIADIC_TO_SEQ(a, b, c) // (a)(b)(c) BOOST_PP_VARIADIC_TO_TUPLE(a, b, c) // (a, b, c)
となります。
これらの関数でも、0引数での呼び出し(例:BOOST_PP_ARRAY()など)では0要素目が空トークン列である1要素の各構造を持つトークン列を作ります。
BOOST_PP_OVERLOAD
この関数は、引数の数によって関数を擬似的にオーバーロードするための関数です。あるいは引数の数に応じて別の関数へとディスパッチするための関数と言ったほうが分かりやすいと思う方もいらっしゃるでしょう。次の例は、引数を全てトークン連結する関数です。
#define CAT(...) BOOST_PP_OVERLOAD(CAT_, __VA_ARGS__)(__VA_ARGS__) #define CAT_1(a0) a0 #define CAT_2(a0, a1) BOOST_PP_CAT(a0, CAT_1(a1)) #define CAT_3(a0, a1, a2) BOOST_PP_CAT(a0, CAT_2(a1, a2)) #define CAT_4(a0, a1, a2, a3) BOOST_PP_CAT(a0, CAT_3(a1, a2, a3))
早い話、BOOST_PP_OVARLOAD(F, トークン列) でトークン列の長さを調べてからその長さをFに連結するだけの関数です。これによってBOOST_PP_TUPLE_*はタプルのサイズが与えられているかどうかを認識して挙動を変えているのです。
その他
使用中のコンパイラが可変長マクロ引数に対応しているかどうかを調べるために、BOOST_PP_VARIADICSという識別子が定義されています。これが1である場合、この記事で紹介している各種新機能が利用できます。
ただしこの識別子、Clangについてはガン無視なので自分で定義する必要があります。