web-dev-qa-db-fra.com

Peut-on avoir des macros récursives?

Je veux savoir si on peut avoir des macros récursives en C/C++? Si oui, veuillez fournir un exemple.

Deuxième chose: pourquoi ne puis-je pas exécuter le code ci-dessous? Quelle est l'erreur que je fais? Est-ce à cause des macros récursives?

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

Votre compilateur fournit probablement une option pour prétraiter uniquement, pas réellement pour compiler. Ceci est utile si vous essayez de trouver un problème dans une macro. Par exemple, en utilisant 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();
}

Comme vous pouvez le voir, ce n'est pas récursif. pr(x) n'est remplacée qu'une seule fois lors du prétraitement. La chose importante à retenir est que tout ce que le pré-processeur fait est de remplacer aveuglément une chaîne de texte par une autre, il n'évalue pas réellement les expressions comme (x == 1).

La raison pour laquelle votre code ne compilera pas est que pr(5 -1) n'a pas été remplacé par le pré-processeur, il finit donc dans la source en tant qu'appel à une fonction non définie.

20
verdesmarald

Les macros ne se développent pas directement récursivement, mais il existe des solutions de contournement. Lorsque le préprocesseur scanne et développe pr(5):

pr(5)
^

il crée un contexte de désactivation, de sorte que quand il revoit pr:

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

il devient peint en bleu et ne peut plus s'étendre, quoi que nous essayions. Mais nous pouvons empêcher notre macro de devenir bleu en utilisant des expressions différées et une certaine indirection:

# 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))

Alors maintenant, il va s'étendre comme ceci:

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

Ce qui est parfait, car pr n'a jamais été peint en bleu. Il nous suffit d'appliquer un autre scan pour l'étendre davantage:

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

Nous pouvons appliquer deux analyses pour l'étendre davantage:

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

Cependant, comme il n'y a pas de condition de résiliation, nous ne pouvons jamais appliquer suffisamment d'analyses. Je ne sais pas ce que vous voulez accomplir, mais si vous êtes curieux de savoir comment créer des macros récursives, voici un exemple de la façon de créer une macro de répétition récursive.

D'abord une macro pour appliquer beaucoup d'analyses:

#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__

Ensuite, une macro concat qui est utile pour la correspondance de motifs:

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

Compteurs d'incrémentation et de décrémentation:

#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

Quelques macros utiles pour les conditions:

#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)

En mettant tout cela ensemble, nous pouvons créer une macro répétée:

#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

Donc, oui, avec certaines solutions de contournement, vous pouvez avoir des macros récursives en C/C++.

107
Paul Fultz II

Vous n'êtes pas censé avoir des macros récursives en C ou C++.

Le langage pertinent de la norme C++, section 16.3.4 paragraphe 2:

Si le nom de la macro en cours de remplacement est trouvé lors de cette analyse de la liste de remplacement (à l'exclusion du reste des jetons de prétraitement du fichier source), il n'est pas remplacé. En outre, si des remplacements imbriqués rencontrent le nom de la macro à remplacer, il n'est pas remplacé. Ces jetons de prétraitement de nom de macro non remplacés ne sont plus disponibles pour un remplacement ultérieur même s'ils sont (ré) examinés ultérieurement dans des contextes dans lesquels ce jeton de prétraitement de nom de macro aurait sinon été remplacé.

Il y a une marge de manœuvre dans cette langue. Avec plusieurs macros qui s'appellent les unes les autres, il y a une zone grise où ce libellé ne dit pas tout à fait ce qui devrait être fait. Il y a un problème actif contre la norme C++ concernant ce problème d'avocat de langue; voir http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#268 .

Ignorant ce problème d'avocat linguistique, chaque fournisseur de compilateur comprend l'intention:

Les macros récursives ne sont pas autorisées en C ou en C++.

18
David Hammen

Vous n'êtes probablement pas en mesure de l'exécuter car vous ne pouvez pas le compiler. De plus, s'il compilait correctement, il retournerait toujours 1. Vouliez-vous dire (n==1)? 1 : n * pr(n-1).

Les macros ne peuvent pas être récursives. Selon le chapitre 16.3.4.2 (merci Loki Astari), si la macro actuelle est trouvée dans la liste de remplacement, elle est laissée telle quelle, ainsi votre pr dans la définition ne sera pas modifié:

Si le nom de la macro en cours de remplacement est trouvé lors de cette analyse de la liste de remplacement (à l'exclusion du reste des jetons de prétraitement du fichier source), il n'est pas remplacé. En outre, si des remplacements imbriqués rencontrent le nom de la macro en cours de remplacement, il n'est pas remplacé. Ces jetons de prétraitement de nom de macro non remplacés ne sont plus disponibles pour un remplacement ultérieur même s'ils sont (ré) examinés ultérieurement dans des contextes dans lesquels ce jeton de prétraitement de nom de macro aurait sinon été remplacé.

Ton appel:

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

a été converti par le préprocesseur en:

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

Pendant cela, la définition de la macro pr est 'perdue' et le compilateur affiche une erreur comme "'pr' n'a pas été déclarée dans cette portée (fait)" car il n'y a pas de fonction nommée pr .

L'utilisation de macros n'est pas encouragée en C++. Pourquoi n'écrivez-vous pas simplement une fonction?

Dans ce cas, vous pouvez même écrire une fonction de modèle afin qu'elle soit résolue au moment de la compilation et se comportera comme une valeur constante:

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

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

Vous ne pouvez pas avoir de macros récursives en C ou C++.

0
sth