web-dev-qa-db-ja.com

再帰的なマクロを使用できますか?

C/C++で再帰マクロを使用できるかどうかを知りたいですか?はいの場合、サンプル例を提供してください。

2番目:なぜ次のコードを実行できないのですか?私がしている間違いは何ですか?再帰的なマクロが原因ですか?

# define pr(n) ((n==1)? 1 : pr(n-1))
void main ()
{
    int a=5;
    cout<<"result: "<< pr(5) <<endl;
    getch();
}
50
user1367292

コンパイラーはおそらく、実際にコンパイルするのではなく、前処理のみのオプションを提供します。これは、マクロの問題を見つけようとしている場合に便利です。たとえば、_g++ -E_を使用する場合:

_> g++ -E recursiveMacro.c

# 1 "recursiveMacro.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "recursiveMacro.c"

void main ()
{
    int a=5;
    cout<<"result: "<< ((5==1)? 1 : pr(5 -1)) <<endl;
    getch();
}
_

ご覧のとおり、再帰的ではありません。 pr(x)は、前処理中に1回だけ置き換えられます。覚えておくべき重要なことは、プリプロセッサがやみくもに1つのテキスト文字列を別のテキスト文字列に置き換えることです。実際には_(x == 1)_のような式を評価しません。

コードがコンパイルされない理由は、pr(5 -1)がプリプロセッサに置き換えられていないため、未定義の関数への呼び出しとしてソースで終了するためです。

20
verdesmarald

マクロは再帰的に直接展開されませんが、回避策があります。プリプロセッサがpr(5)をスキャンして展開する場合:

pr(5)
^

無効化コンテキストが作成されるため、prが再び表示されるようになります。

((5==1)? 1 : pr(5-1))
             ^

それは青く塗られ、私たちが何をしようとしてももはや拡大できなくなります。ただし、遅延式といくつかのインダイレクションを使用して、マクロが青く塗られないようにすることができます。

# define EMPTY(...)
# define DEFER(...) __VA_ARGS__ EMPTY()
# define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
# define EXPAND(...) __VA_ARGS__

# define pr_id() pr
# define pr(n) ((n==1)? 1 : DEFER(pr_id)()(n-1))

そのため、次のように拡張されます。

pr(5) // Expands to ((5==1)? 1 : pr_id ()(5 -1))

prは決して青く塗られていないため、これは完璧です。別のスキャンを適用して、さらに拡張する必要があります。

EXPAND(pr(5)) // Expands to ((5==1)? 1 : ((5 -1==1)? 1 : pr_id ()(5 -1 -1)))

2つのスキャンを適用して、さらに拡張することができます。

EXPAND(EXPAND(pr(5))) // Expands to ((5==1)? 1 : ((5 -1==1)? 1 : ((5 -1 -1==1)? 1 : pr_id ()(5 -1 -1 -1))))

ただし、終了条件がないため、十分なスキャンを適用することはできません。何を達成したいかはわかりませんが、再帰マクロの作成方法に興味がある場合は、再帰繰り返しマクロの作成方法の例を次に示します。

最初に多くのスキャンを適用するマクロ:

#define EVAL(...)  EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL1(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL2(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL3(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define EVAL4(...) EVAL5(EVAL5(EVAL5(__VA_ARGS__)))
#define EVAL5(...) __VA_ARGS__

次に、パターンマッチングに役立つconcatマクロ:

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

増分および減分カウンター:

#define INC(x) PRIMITIVE_CAT(INC_, x)
#define INC_0 1
#define INC_1 2
#define INC_2 3
#define INC_3 4
#define INC_4 5
#define INC_5 6
#define INC_6 7
#define INC_7 8
#define INC_8 9
#define INC_9 9

#define DEC(x) PRIMITIVE_CAT(DEC_, x)
#define DEC_0 0
#define DEC_1 0
#define DEC_2 1
#define DEC_3 2
#define DEC_4 3
#define DEC_5 4
#define DEC_6 5
#define DEC_7 6
#define DEC_8 7
#define DEC_9 8

条件に役立ついくつかのマクロ:

#define CHECK_N(x, n, ...) n
#define CHECK(...) CHECK_N(__VA_ARGS__, 0,)

#define NOT(x) CHECK(PRIMITIVE_CAT(NOT_, x))
#define NOT_0 ~, 1,

#define COMPL(b) PRIMITIVE_CAT(COMPL_, b)
#define COMPL_0 1
#define COMPL_1 0

#define BOOL(x) COMPL(NOT(x))

#define IIF(c) PRIMITIVE_CAT(IIF_, c)
#define IIF_0(t, ...) __VA_ARGS__
#define IIF_1(t, ...) t

#define IF(c) IIF(BOOL(c))

#define EAT(...)
#define EXPAND(...) __VA_ARGS__
#define WHEN(c) IF(c)(EXPAND, EAT)

すべてをまとめると、繰り返しマクロを作成できます。

#define REPEAT(count, macro, ...) \
    WHEN(count) \
    ( \
        OBSTRUCT(REPEAT_INDIRECT) () \
        ( \
            DEC(count), macro, __VA_ARGS__ \
        ) \
        OBSTRUCT(macro) \
        ( \
            DEC(count), __VA_ARGS__ \
        ) \
    )
#define REPEAT_INDIRECT() REPEAT

//An example of using this macro
#define M(i, _) i
EVAL(REPEAT(8, M, ~)) // 0 1 2 3 4 5 6 7

そのため、いくつかの回避策を使用すると、C/C++で再帰マクロを使用できます。

107
Paul Fultz II

CまたはC++で再帰マクロを使用することは想定されていません

C++標準のセクション16.3.4段落2の関連言語:

置換リストのこのスキャン中に置換されるマクロの名前が見つかった場合(ソースファイルの前処理トークンの残りは含まれません)、置換されません。さらに、入れ子になった置換が置換されるマクロの名前に遭遇した場合、置換されません。これらの置換されていないマクロ名の前処理トークンは、マクロ名の前処理トークンが置換されたコンテキストで後で(再)検査された場合でも、それ以上の置換には使用できなくなります。

この言語にはいくつかのゆらぎがあります。相互に呼び出す複数のマクロを使用すると、その言葉遣いが何をすべきかを明確に示していない灰色の領域があります。この言語弁護士の問題に関して、C++標準に対する積極的な問題があります。 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#268 を参照してください。

その言語弁護士の問題を無視して、すべてのコンパイラベンダーはその意図を理解しています。

再帰マクロは、CまたはC++では使用できません。

18
David Hammen

ほとんどの場合、コンパイルできないため実行できません。また、正しくコンパイルされると、常に1が返されます。つまり、(n==1)? 1 : n * pr(n-1)

マクロは再帰できません。 16.3.4.2章(Loki Astariに感謝)によると、現在のマクロが置換リストで見つかった場合、そのまま残されるため、定義のprは変更されません。

置換リストのこのスキャン中に置換されるマクロの名前が見つかった場合(ソースファイルの前処理トークンの残りは含まない)、置換されません。さらに、ネストされた置換が置換されるマクロの名前に遭遇した場合、置換されません。これらの置換されていないマクロ名の前処理トークンは、マクロ名の前処理トークンが置換されたコンテキストで後で(再)検査された場合でも、それ以上の置換には使用できなくなります。

あなたの電話:

cout<<"result: "<< pr(5) <<endl;

プリプロセッサによって次のように変換されました:

cout<<"result: "<< (5==1)? 1 : pr(5-1) <<endl;

この間、prマクロの定義は 'lost'であり、prという名前の関数がないため、コンパイラは「 'pr' was not defined in this scope(fact)」などのエラーを表示します。

C++ではマクロの使用は推奨されていません。関数を書いてみませんか?

この場合、コンパイル時に解決され、定数値として動作するように、テンプレート関数を作成することもできます。

template <int n>
int pr() {  pr<n-1>(); }

template <>
int pr<1>() { return 1; }
11

CまたはC++では再帰的なマクロを使用できません。

0
sth