48 votes

Les macros récursives variadiques du préprocesseur - est-ce possible ?

J'ai rencontré un petit problème théorique. Dans un morceau de code que je maintiens, il y a un ensemble de macros comme

#define MAX_OF_2(a, b)       (a) > (b) ? (a) : (b)
#define MAX_OF_3(a, b, c)    MAX_OF_2(MAX_OF_2(a, b), c)
#define MAX_OF_4(a, b, c, d) MAX_OF_2(MAX_OF_3(a, b, c), d)
...etc up to MAX_OF_8

Ce que j'aimerais faire, c'est les remplacer par quelque chose comme ça :

/* Base case #1, single input */
#define MAX_OF_N(x)      (x)

/* Base case #2, two inputs */
#define MAX_OF_N(x, y)   (x) > (y) ? (x) : (y)

/* Recursive definition, arbitrary number of inputs */
#define MAX_OF_N(x, ...) MAX_OF_N(x, MAX_OF_N(__VA_ARGS__))

...qui, bien sûr, n'est pas un code de préprocesseur valide.

Ignorant que ce cas particulier devrait probablement être résolu en utilisant une fonction plutôt qu'une macro de préprocesseur est-il possible de définir une macro variadique MAX_OF_N() ?

Par souci de clarté, le résultat final devrait être une seule macro qui prend un nombre arbitraire de paramètres et évalue le plus grand d'entre eux. J'ai le sentiment étrange que cela devrait être possible, mais je ne vois pas comment.

11 votes

+1 pour le tag "preprocessor-abuse" :)

1 votes

stackoverflow.com/questions/6707148 démontre la récursion mutuelle (la réponse la plus votée mais non acceptée)

48voto

DS. Points 3577

Il est possible d'écrire une macro dont l'évaluation correspond au nombre d'arguments avec lesquels elle est appelée. (Ainsi, vous pourriez écrire MAX_OF_N() qui fonctionnerait comme vous le souhaitez, mais vous auriez toujours besoin de toutes les macros numérotées jusqu'à une certaine limite :

#define MAX_OF_1(a)         (a)         
#define MAX_OF_2(a,b)       max(a, b)

#define MAX_OF_3(a,...)    MAX_OF_2(a,MAX_OF_2(__VA_ARGS__))
#define MAX_OF_4(a,...)    MAX_OF_2(a,MAX_OF_3(__VA_ARGS__))
#define MAX_OF_5(a,...)    MAX_OF_2(a,MAX_OF_4(__VA_ARGS__))
...
#define MAX_OF_64(a,...)   MAX_OF_2(a,MAX_OF_63(__VA_ARGS__))

// NUM_ARGS(...) evaluates to the literal number of the passed-in arguments.
#define _NUM_ARGS2(X,X64,X63,X62,X61,X60,X59,X58,X57,X56,X55,X54,X53,X52,X51,X50,X49,X48,X47,X46,X45,X44,X43,X42,X41,X40,X39,X38,X37,X36,X35,X34,X33,X32,X31,X30,X29,X28,X27,X26,X25,X24,X23,X22,X21,X20,X19,X18,X17,X16,X15,X14,X13,X12,X11,X10,X9,X8,X7,X6,X5,X4,X3,X2,X1,N,...) N
#define NUM_ARGS(...) _NUM_ARGS2(0, __VA_ARGS__ ,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)

#define _MAX_OF_N3(N, ...) MAX_OF_ ## N(__VA_ARGS__)
#define _MAX_OF_N2(N, ...) _MAX_OF_N3(N, __VA_ARGS__)
#define MAX_OF_N(...)      _MAX_OF_N2(NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

Maintenant MAX_OF_N(a,b,c,d,e) évaluera à max(a, max(b, max(c, max(d, e)))) . (J'ai testé sur gcc 4.2.1).

Notez qu'il est essentiel que le cas de base ( MAX_OF_2 ) ne répète pas ses arguments plus d'une fois dans l'expansion (c'est pourquoi j'ai mis max dans cet exemple). Sinon, vous doubleriez la longueur de l'extension pour chaque niveau, donc vous pouvez imaginer ce qui se passera avec 64 arguments :)

0 votes

Pourquoi avez-vous _MAX_OF_N2 Quelle est sa fonction ?

2 votes

C'est juste pour forcer un niveau d'expansion. Si j'avais mis _MAX_OF_N3(NUM_ARGS( VA_ARGS ), etc)", en sautant l'étape intermédiaire, il aurait été étendu à MAX_OF_NUMARGS( VA_ARGS )(etc).

0 votes

@Ameen Les macros variadiques ont été introduites dans le langage C en 1999. Visual Studio "2017" n'est pas assez moderne, car il n'a pas encore été entièrement mis à jour pour l'année 1999. Utilisez plutôt un compilateur C moderne.

13voto

Steve Jessop Points 166970

Vous pouvez considérer que c'est de la triche, puisque ce n'est pas récursif et que le travail n'est pas effectué dans le préprocesseur. Et elle utilise une extension GCC. Et elle ne fonctionne que pour un seul type. Il s'agit cependant d'une macro MAX_OF_N variadique :

#include <iostream>
#include <algorithm>

#define MAX_OF_N(...) ({\
        int ra[] = { __VA_ARGS__ }; \
        *std::max_element(&ra[0], &ra[sizeof(ra)/sizeof(int)]); \
    })

int main() {
    int i = 12;
    std::cout << MAX_OF_N(1,3,i,6);
}

Ah oui, et à cause de l'expression variable potentielle dans la liste des initialisateurs, je ne pense pas qu'un équivalent de ceci (utilisant sa propre fonction pour éviter std::max_element) fonctionnerait en C89. Mais je ne suis pas sûr que les macros variadiques soient en C89 non plus.

Voici quelque chose qui, je pense, permet de contourner la restriction "un seul type". Mais ça devient un peu compliqué :

#include <iostream>
#include <algorithm>

#define MAX_OF_N(x, ...) ({\
        typeof(x) ra[] = { (x), __VA_ARGS__ }; \
        *std::max_element(&ra[0], &ra[sizeof(ra)/sizeof(ra[0])]); \
    })

int main() {
    int i = 12;
    std::cout << MAX_OF_N(i+1,1,3,6,i);
}

3 votes

À ma connaissance, les macros variadiques ne font pas partie du C++, donc presque toutes les combinaisons deviennent problématiques :)

0 votes

Ah, bon point, bien qu'ils soient disponibles par défaut dans g++. En fait, je pensais que c'était difficile parce que le premier élément de la liste des paramètres n'est pas nécessairement celui qui a le type commun le plus sensible. De très mauvaises choses pourraient se produire si l'initialisation du tableau découpe des objets ou (en C) effectue des conversions inappropriées.

12voto

paxdiablo Points 341644

Non, parce que le préprocesseur ne prend qu'un seul "coup" au fichier. Il n'y a aucun moyen de lui faire définir des macros de manière récursive.

Le seul code que j'ai vu faire quelque chose comme ça était no variadique, mais utilisait des valeurs par défaut que l'utilisateur devait passer :

x = MAX_OF_8 (a, b, -1, -1, -1, -1, -1, -1)

en supposant que toutes les valeurs sont non négatives.

Les fonctions en ligne devraient vous donner la même chose pour le C++ au moins. Comme vous le dites, il est probablement préférable d'utiliser une fonction avec des arguments variables, comme dans le cas suivant printf() .

0 votes

J'accepte cette réponse comme la plus correcte, il n'est pas (et ne devrait pas être) possible de créer des instructions récursives dans le préprocesseur.

6 votes

C'est censé être impossible, mais pas parce que le préprocesseur est à passe unique. Le processus de substitution de macro est récursif et à passes multiples, en raison de ce que les normes appellent "rescanning and further replacement". La récursion et la corecursion sont interdites par un mécanisme spécifiquement défini. Mais cela peut toujours être contourné. En ce qui concerne la récursion en utilisant cette syntaxe c'est impossible car l'absence de surcharge empêche d'écrire un cas de terminaison.

1 votes

Vous pouvez demander au préprocesseur d'effectuer plusieurs passages sur un fichier avec #include __FILE__ .

7voto

fortran Points 26495

Je pense que, même si vous pouviez développer les macros de manière récursive, il y aurait un petit problème avec votre approche en termes d'efficacité... lorsque les macros sont développées, si le MAX_OF_[N-1] est supérieure, vous devez alors l'évaluer à nouveau en partant de zéro.

Voici une réponse idiote et stupide qui ne plaira probablement à personne xD

Fichier "Source.c".

#include "my_macros.h"
...

fichier "Makefile

myprogram: source.c my_macros.h
 gcc source.c -o myprogram

my_macros.h: make_macros.py
 python make_macros.py > my_macros.h

Fichier " make_macros.py ".

def split(l):
    n = len(l)
    return l[:n/2], l[n/2:]

def gen_param_seq(n):
    return [chr(i + ord("A")) for i in range(n)]

def make_max(a, b):
    if len(a) == 1:
        parta = "("+a[0]+")"
    else:
        parta = make_max(*split(a))

    if len(b) == 1:
        partb = "("+b[0]+")"
    else:
        partb = make_max(*split(b))

    return "("+parta +">"+partb+"?"+parta+":"+partb+")"

for i in range(2, 9):
    p = gen_param_seq(i)
    print "#define MAX_"+str(i)+"("+", ".join(p)+") "+make_max(*split(p))

alors vous aurez ces jolies macros définies :

#define MAX_2(A, B) ((A)>(B)?(A):(B))
#define MAX_3(A, B, C) ((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C)))
#define MAX_4(A, B, C, D) (((A)>(B)?(A):(B))>((C)>(D)?(C):(D))?((A)>(B)?(A):(B)):((C)>(D)?(C):(D)))
#define MAX_5(A, B, C, D, E) (((A)>(B)?(A):(B))>((C)>((D)>(E)?(D):(E))?(C):((D)>(E)?(D):(E)))?((A)>(B)?(A):(B)):((C)>((D)>(E)?(D):(E))?(C):((D)>(E)?(D):(E))))
#define MAX_6(A, B, C, D, E, F) (((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C)))>((D)>((E)>(F)?(E):(F))?(D):((E)>(F)?(E):(F)))?((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C))):((D)>((E)>(F)?(E):(F))?(D):((E)>(F)?(E):(F))))
#define MAX_7(A, B, C, D, E, F, G) (((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C)))>(((D)>(E)?(D):(E))>((F)>(G)?(F):(G))?((D)>(E)?(D):(E)):((F)>(G)?(F):(G)))?((A)>((B)>(C)?(B):(C))?(A):((B)>(C)?(B):(C))):(((D)>(E)?(D):(E))>((F)>(G)?(F):(G))?((D)>(E)?(D):(E)):((F)>(G)?(F):(G))))
#define MAX_8(A, B, C, D, E, F, G, H) ((((A)>(B)?(A):(B))>((C)>(D)?(C):(D))?((A)>(B)?(A):(B)):((C)>(D)?(C):(D)))>(((E)>(F)?(E):(F))>((G)>(H)?(G):(H))?((E)>(F)?(E):(F)):((G)>(H)?(G):(H)))?(((A)>(B)?(A):(B))>((C)>(D)?(C):(D))?((A)>(B)?(A):(B)):((C)>(D)?(C):(D))):(((E)>(F)?(E):(F))>((G)>(H)?(G):(H))?((E)>(F)?(E):(F)):((G)>(H)?(G):(H))))

et le plus beau dans tout ça, c'est que... ça marche ^_^

1 votes

Hah, j'aime bien ça. Comment ne pas aimer l'utilisation de python comme préprocesseur C ? :)

1 votes

Vous pouvez utiliser n'importe quel langage cool que vous aimez comme préprocesseur... et étant donné la quantité de parens, peut-être que lisp pourrait être très amusant ! XD ou peut-être un Perl one liner pour que vous puissiez passer les macros via le drapeau -D au compilateur dans la ligne de commande ;-)

5voto

Jason S Points 58434

Si vous vous lancez dans cette voie en C++, jetez un coup d'œil à métaprogrammation par modèle . Ce n'est pas très joli, et cela ne résoudra peut-être pas votre problème exact, mais cela permet de gérer la récursion.

2 votes

+1. Les modèles variadiques sont prévus dans C++1x, et g++ les implémente déjà, mais il faudra un certain temps avant qu'ils ne soient largement acceptés. Malheureusement, une solution de métaprogrammation par gabarit sans eux nécessitera toujours une imbrication laide des termes (par exemple "max_of<3, max_of<4, max_of<5, 6> > >") à moins de les "dérouler" manuellement (ce que vous pourriez faire tout aussi facilement avec des macros de préprocesseur). Les modèles sont encore meilleurs parce qu'ils peuvent récurer et n'évaluent pas les arguments plus d'une fois.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X