Bien que ce soit une mauvaise idée, cela peut être fait, alors voici un moyen.
Vous pouvez en fait faire des boucles dans le préprocesseur et définir des macros récursives si vous utilisez une bibliothèque de métaprogrammation suffisamment puissante telle que Commandez (comme mentionné ci-dessus, Boost est un autre candidat possible). Order vous permet de programmer dans un style fonctionnel familier à tous ceux qui connaissent Scheme ou ML.
Pour boucler, utilisez un for_each
construire. Pour créer simplement un nombre donné de choses, vous pouvez utiliser for_each_in_range
con 1, N+1
:
ORDER_PP( // within this block Order code runs
8for_each_in_range(8fn(8_, 8print( (*) ) ),
1, 8)
)
Le texte ci-dessus imprimera 7 étoiles. Vous pouvez envelopper les blocs de métaprogrammes dans des macros conventionnelles, qui suivent les règles normales du préprocesseur :
// print COUNT stars
#define STARS(COUNT) ORDER_PP( \
8for_each_in_range(8fn(8_, 8print((*)) ), 1, 8plus(COUNT, 1)) \
)
Dans le cadre de la ORDER_PP
tout est supposé être du code de l'Ordre plutôt que du code du préprocesseur C, ce qui signifie que seules les fonctions reconnues de l'Ordre peuvent être appelées (et que toutes les valeurs/jetons de prétraitement doivent être soit des ints bruts, soit "cités" avec la balise 8(val)
construire). Pour définir stars
comme une fonction d'ordre au lieu d'une macro CPP, de sorte qu'elle puisse être appelée à partir de l'intérieur de ORDER_PP
dans le cadre d'une expression imbriquée, nous devons l'écrire comme ceci :
#define ORDER_PP_DEF_8stars ORDER_PP_FN( \
8fn(8C, 8for_each_in_range(8fn(8_, 8print((*)) ), 1, 8plus(8C, 1)) ))
ORDER_PP( 8stars(7) ) // prints 7 stars
Order fournit la récursion de manière totalement transparente, de sorte que l'écriture des boucles d'initialisation imbriquées est relativement simple :
#define ORDER_PP_DEF_8ndim_init ORDER_PP_FN( \
8fn(8N, 8T, 8C, 8I, 8D, \
8do( \
8print( 8N (=malloc) 8lparen 8seq_head(8D) (*sizeof) 8lparen 8T 8stars(8minus(8C, 1)) 8rparen 8rparen (;) ), \
8if(8equal(8C, 1), \
8print(((void)0;)), \
8do( \
8print( (for) 8lparen (int) 8I (=0;) 8I (<) 8seq_head(8D) (;) 8I (++) 8rparen ({) ), \
8ndim_init(8adjoin(8N, 8([), 8I, 8(])), 8T, 8minus(8C, 1), 8cat(8I, 8(_K)), 8seq_tail(8D)), \
8print( (}) ) \
)))))
Appeler ndim_init
comme ça :
// print the nested initializer from the question
ORDER_PP(
8ndim_init(8(nda), 8(int), 3, 8(i), 8seq(8(n1), 8(n2), 8(n3)))
)
Remarquez que les noms de variables C ( nda
, i
etc.) doivent être citées lorsqu'elles apparaissent dans le texte de l'accord. ORDER_PP
afin qu'Order les traite comme du texte, au lieu d'essayer de les évaluer. Le dernier argument est une liste des variables d'exécution qui contiendront les tailles pour chaque dimension ( 8seq
construit une liste, 8
cite à nouveau les noms des variables C).
Vous pouvez empaqueter l'invocation de ndim_init
dans une macro de préprocesseur ordinaire pour un accès facile, tout comme le premier exemple avec STARS
; vous pouvez facilement la combiner avec une macro de déclaration de cette manière, pour émettre la déclaration et l'initialisation en un seul appel :
#define NDIM(NAME, TYPE, ...) ORDER_PP ( \
8lets( (8D, 8((__VA_ARGS__))) \
(8C, 8tuple_size(8D)), \
8do( \
8print( (TYPE) 8stars(8C) (NAME; {) ), \
8ndim_init(8(NAME), 8(TYPE), 8C, 8(_ITER), 8tuple_to_seq(8D)), \
8print( (}) ) \
)) \
)
NDIM(nda, int, n1, n2, n3) // emits declaration and init block for int ***nda
Autres exemples de commandes
Si ce qui précède ne vous a pas semblé simple du tout... c'est pourquoi les gens disent que vous ne devriez pas le faire. (Si c'est le cas, tant mieux pour vous, personne d'autre ne sera capable de lire votre code cependant).