最善のアプローチは、プリプロセッサを使用して解析することです。プリプロセッサはEDSL(組み込みドメイン固有言語)を構築するための非常に強力なツールになると思いますが、最初にプリプロセッサの解析の制限を理解する必要があります。プリプロセッサは、事前定義されたトークンのみを解析できます。したがって、式の前後に括弧を配置して構文を少し変更する必要があり、FREEZE
マクロもそれを囲む必要があります(FREEZEを選択しただけで、何でも呼び出すことができます)。
FREEZE(take(x*x) with(x, container))
この構文を使用すると、プリプロセッサシーケンスに変換できます(もちろん、Boost.Preprocessorライブラリを使用します)。プリプロセッサシーケンスとして取得したら、多くのアルゴリズムを適用して、好きなように変換できます。同様のアプローチは、C ++用のLinqライブラリを使用して実行されます。ここでは、次のように記述できます。
LINQ(from(x, numbers) where(x > 2) select(x * x))
ここで、最初にppシーケンスに変換するには、次のように、解析するキーワードを定義する必要があります。
#define KEYWORD(x) BOOST_PP_CAT(KEYWORD_, x)
#define KEYWORD_take (take)
#define KEYWORD_with (with)
したがって、これが機能する方法は、呼び出すときにKEYWORD(take(x*x) with(x, container))
に展開され(take)(x*x) with(x, container)
ます。これは、ppシーケンスに変換するための最初のステップです。続行するには、Boost.Preprocessorライブラリからwhileコンストラクトを使用する必要がありますが、最初に、途中で役立ついくつかの小さなマクロを定義する必要があります。
// Detects if the first token is parenthesis
#define IS_PAREN(x) IS_PAREN_CHECK(IS_PAREN_PROBE x)
#define IS_PAREN_CHECK(...) IS_PAREN_CHECK_N(__VA_ARGS__,0)
#define IS_PAREN_PROBE(...) ~, 1,
#define IS_PAREN_CHECK_N(x, n, ...) n
// Detect if the parameter is empty, works even if parenthesis are given
#define IS_EMPTY(x) BOOST_PP_CAT(IS_EMPTY_, IS_PAREN(x))(x)
#define IS_EMPTY_0(x) BOOST_PP_IS_EMPTY(x)
#define IS_EMPTY_1(x) 0
// Retrieves the first element of the sequence
// Example:
// HEAD((1)(2)(3)) // Expands to (1)
#define HEAD(x) PICK_HEAD(MARK x)
#define MARK(...) (__VA_ARGS__),
#define PICK_HEAD(...) PICK_HEAD_I(__VA_ARGS__,)
#define PICK_HEAD_I(x, ...) x
// Retrieves the tail of the sequence
// Example:
// TAIL((1)(2)(3)) // Expands to (2)(3)
#define TAIL(x) EAT x
#define EAT(...)
これにより、括弧と空の検出が向上します。また、とは少し異なる動作をするマクロをHEAD
提供します。(Boost.Preprocessorは、vardiacパラメーターを持つシーケンスを処理できません)。ここで、while構文を使用するマクロを定義する方法を説明します。TAIL
BOOST_PP_SEQ_HEAD
TO_SEQ
#define TO_SEQ(x) TO_SEQ_WHILE_M \
( \
BOOST_PP_WHILE(TO_SEQ_WHILE_P, TO_SEQ_WHILE_O, (,x)) \
)
#define TO_SEQ_WHILE_P(r, state) TO_SEQ_P state
#define TO_SEQ_WHILE_O(r, state) TO_SEQ_O state
#define TO_SEQ_WHILE_M(state) TO_SEQ_M state
#define TO_SEQ_P(prev, tail) BOOST_PP_NOT(IS_EMPTY(tail))
#define TO_SEQ_O(prev, tail) \
BOOST_PP_IF(IS_PAREN(tail), \
TO_SEQ_PAREN, \
TO_SEQ_KEYWORD \
)(prev, tail)
#define TO_SEQ_PAREN(prev, tail) \
(prev (HEAD(tail)), TAIL(tail))
#define TO_SEQ_KEYWORD(prev, tail) \
TO_SEQ_REPLACE(prev, KEYWORD(tail))
#define TO_SEQ_REPLACE(prev, tail) \
(prev HEAD(tail), TAIL(tail))
#define TO_SEQ_M(prev, tail) prev
これで、呼び出すときにTO_SEQ(take(x*x) with(x, container))
シーケンスを取得する必要があります(take)((x*x))(with)((x, container))
。
これで、このシーケンスの操作がはるかに簡単になりました(Boost.Preprocessorライブラリがあるため)。これで、反転、変換、フィルタリング、折り畳みなどを行うことができます。これは非常に強力であり、マクロとして定義するよりもはるかに柔軟性があります。たとえば、Linqライブラリでは、クエリfrom(x, numbers) where(x > 2) select(x * x)
は次のマクロに変換されます。
LINQ_WHERE(x, numbers)(x > 2) LINQ_SELECT(x, numbers)(x * x)
これらのマクロのどれを使用すると、リスト内包表記のラムダが生成されますが、ラムダを生成するときに使用できる機能はさらに多くなります。ライブラリでも同じことができ、take(x*x) with(x, container)
次のように変換できます。
FREEZE_TAKE(x, container, x*x)
take
さらに、グローバル空間に侵入するようなマクロを定義しているわけではありません。
注:ここでのこれらのマクロにはC99プリプロセッサーが必要であるため、MSVCでは機能しません(回避策はあります)