¿Cómo escribo una macro recursiva para "repetir" bucle para generar el código C con el preprocesador CPP?
recursion macros (4)
Quiero forzar al preprocesador a hacer algo de generación automática de código para mí. No necesito mucho: solo un for-loop simple que contiene otro for-loop. [1]
He leído todo lo que puedo sobre la expansión macro, y ya no me río cuando aparece la pintura azul. En un buen día, incluso puedo explicar por qué uno necesita múltiples capas de macros para generar un nombre de función con el pegado de tokens. De hecho, tengo el for-loop funcionando. Pero cuando se trata de poner un ciclo dentro de un ciclo, me veo reducido a rociar aleatoriamente con DEFER, EVAL y OBSTRUCT y esperando lo mejor.
No me disuadirán las llamadas a la razón. Realmente quiero hacer esto con el preprocesador C estándar. Prometo que independientemente del resultado, ni yo, mi empleador ni mis herederos lo demandaremos por negligencia tecnológica. Prometo que no permitiré que nadie más mantenga el código, o incluso lo vea, sin las gafas de seguridad adecuadas. Imagina si quieres que solo pregunte por interés teórico. O que mi única otra opción es usar M4: porque si las macros recursivas en CPP son rizadas, ciertamente M4 es el pollo completo.
El mejor material de referencia que he encontrado es un hilo de Usenet de 9 años: http://comp.std.c.narkive.com/5WbJfCof/double-cpp-expansion
Comienza fuera del tema, es algo mezquino y de tono combativo, y está muy por encima de mi cabeza. Pero creo que la respuesta que busco está allí en alguna parte.
El siguiente mejor es la documentación para un encabezado de abuso de CPP llamado Cloak: https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms
Se necesita un enfoque algo diferente a la iteración, y tal vez sirva a mis necesidades en su lugar. Pero también es una buena visión general.
Aquí hay un código de reducción para mostrar dónde estoy atrapado.
repeat.h:
#define REPEAT(macro, times, start_n, next_func, next_arg, macro_args...) /
_REPEAT_ ## times(macro, start_n, next_func, next_arg, ## macro_args)
#define REPEAT_ADD_ONE(macro, times, start_n, macro_args... ) /
REPEAT(macro, times, start_n, _REPEAT_ADD_ONE, 0, ## macro_args)
#define _REPEAT_ADD_ONE(n, ignore...) _REPEAT_ADD_ONE_ ## n
#define _REPEAT_0(args...) /* empty */
#define _REPEAT_1(macro, n, func, i, args...) macro(n, ## args)
#define _REPEAT_2(m, n, f, i, a...) m(n, ## a); _REPEAT_1(m, f(n, i), f, i, ## a)
#define _REPEAT_3(m, n, f, i, a...) m(n, ## a); _REPEAT_2(m, f(n, i), f, i, ## a)
#define _REPEAT_4(m, n, f, i, a...) m(n, ## a); _REPEAT_3(m, f(n, i), f, i, ## a)
#define _REPEAT_5(m, n, f, i, a...) m(n, ## a); _REPEAT_4(m, f(n, i), f, i, ## a)
#define _REPEAT_6(m, n, f, i, a...) m(n, ## a); _REPEAT_5(m, f(n, i), f, i, ## a)
#define _REPEAT_7(m, n, f, i, a...) m(n, ## a); _REPEAT_6(m, f(n, i), f, i, ## a)
#define _REPEAT_8(m, n, f, i, a...) m(n, ## a); _REPEAT_7(m, f(n, i), f, i, ## a)
#define _REPEAT_9(m, n, f, i, a...) m(n, ## a); _REPEAT_8(m, f(n, i), f, i, ## a)
#define _REPEAT_10(m, n, f, i, a...) m(n, ## a); _REPEAT_9(m, f(n, i), f, i, ## a)
#define _REPEAT_ADD_ONE_0 1
#define _REPEAT_ADD_ONE_1 2
#define _REPEAT_ADD_ONE_2 3
#define _REPEAT_ADD_ONE_3 4
#define _REPEAT_ADD_ONE_4 5
#define _REPEAT_ADD_ONE_5 6
#define _REPEAT_ADD_ONE_6 7
#define _REPEAT_ADD_ONE_7 8
#define _REPEAT_ADD_ONE_8 9
#define _REPEAT_ADD_ONE_9 10
#define _REPEAT_ADD_ONE_10 11
#define _REPEAT_ADD_0(x) x
#define _REPEAT_ADD_1(x) _REPEAT_ADD_ONE(x)
#define _REPEAT_ADD_2(x) _REPEAT_ADD_1(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_3(x) _REPEAT_ADD_2(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_4(x) _REPEAT_ADD_3(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_5(x) _REPEAT_ADD_4(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_6(x) _REPEAT_ADD_5(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_7(x) _REPEAT_ADD_6(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_8(x) _REPEAT_ADD_7(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_9(x) _REPEAT_ADD_8(_REPEAT_ADD_ONE(x))
#define _REPEAT_ADD_10(x) _REPEAT_ADD_9(_REPEAT_ADD_ONE(x))
sample.c:
#include "repeat.h"
#define INNER_MACRO(inner, outer) if (inner == outer) printf("Match/n")
#define INNER_BLOCK { if (inner == outer) printf("Match/n"); }
#define OUTER_MACRO_INNER_MACRO(outer) REPEAT_ADD_ONE(INNER_MACRO, 3, 0, outer)
#define OUTER_BLOCK_INNER_MACRO { REPEAT_ADD_ONE(INNER_MACRO, 3, 0, outer); }
#define OUTER_MACRO_INNER_BLOCK(outer) REPEAT_ADD_ONE(INNER_BLOCK, 3, 0, outer)
#define OUTER_BLOCK_INNER_BLOCK { REPEAT_ADD_ONE(INNER_BLOCK, 3, 0, outer); }
void outer_macro_inner_macro() {
REPEAT_ADD_ONE(OUTER_MACRO_INNER_MACRO, 2, 1);
}
void outer_macro_inner_block() {
REPEAT_ADD_ONE(OUTER_MACRO_INNER_BLOCK, 2, 1);
}
void outer_block_inner_macro() {
REPEAT_ADD_ONE(OUTER_BLOCK_INNER_MACRO, 2, 1);
}
void outer_block_inner_block() {
REPEAT_ADD_ONE(OUTER_BLOCK_INNER_BLOCK, 2, 1);
}
En sample.c
, he mostrado cuatro variaciones que se acercan a lo que deseo. Pero ninguno está allí. Esto es lo que obtengo como salida con "cpp sample.c> out.c; astyle out.c;"
void outer_macro_inner_macro() {
REPEAT_ADD_ONE(INNER_MACRO, 3, 0, 1);
REPEAT_ADD_ONE(INNER_MACRO, 3, 0, 2);
}
void outer_macro_inner_block() {
REPEAT_ADD_ONE({ if (inner == outer) printf("Match/n"); }, 3, 0, 1);
REPEAT_ADD_ONE({ if (inner == outer) printf("Match/n"); }, 3, 0, 2);
}
void outer_block_inner_macro() {
{
if (0 == outer) printf("Match/n");
if (1 == outer) printf("Match/n");
if (2 == outer) printf("Match/n");
}(1);
{
if (0 == outer) printf("Match/n");
if (1 == outer) printf("Match/n");
if (2 == outer) printf("Match/n");
}(2);
}
void outer_block_inner_block() {
{ {
if (inner == outer) printf("Match/n");
}(0, outer);
{
if (inner == outer) printf("Match/n");
}(1, outer);
{
if (inner == outer) printf("Match/n");
}(2, outer);
}(1);
{ {
if (inner == outer) printf("Match/n");
}(0, outer);
{
if (inner == outer) printf("Match/n");
}(1, outer);
{
if (inner == outer) printf("Match/n");
}(2, outer);
}(2);
}
Y aquí está la salida que quiero obtener en su lugar:
void desired_results() {
{
if (0 == 1) printf("Match/n");
if (1 == 1) printf("Match/n");
if (2 == 1) printf("Match/n");
};
{
if (0 == 2) printf("Match/n");
if (1 == 2) printf("Match/n");
if (2 == 2) printf("Match/n");
};
}
Básicamente, puedo hacer que las cosas funcionen si uso un bloque como el cuerpo externo del bucle, pero no si utilizo una macro similar a una función. Pero necesito usar una macro con argumentos para que los cuerpos de bucle puedan usar el contador de bucle como una constante en lugar de como una variable.
El problema con la forma "macro" - "macro" es que la llamada recursiva interna a REPEAT_ADD_ONE () no se expande. La respuesta parecería retrasar la expansión del bucle interno hasta que se haya creado el bucle externo, y luego forzar otro pase que expanda el bucle interno. Pero, por alguna razón, mi enfoque de "mono aleatorio" aún no ha producido una solución ...
[1] Understatement previsto.
P99 podría proporcionarle lo que está buscando. Tiene varios tipos de macro iteradores, simples como P99_UNROLL
, P99_SER
, etc. y uno genérico P99_FOR
.
La biblioteca / lenguaje de "Orden" de Vesa Karvonen definitivamente puede hacer esto por usted. Implementa la recursión y bucle sin restricciones en el preprocesador C, y como una bonificación realmente genial, lo viste con la agradable sintaxis concisa de un lenguaje de programación "apropiado" (para aclarar: no es un preprocesador alternativo, simplemente hace un montón de tokens) -pronto para mantener sus palabras clave cortas. Todavía es CPP puro).
Utiliza una técnica bastante diferente, convirtiendo sus metaprogramas a CPS y luego pasándolos a una construcción de bucle único que tiene potencialmente billones de pasos y ejecuta el metaprograma de una manera estrictamente lineal. Por lo tanto, los bucles y las funciones recursivas se pueden anidar tan profundamente como se desee porque no tienen controladores separados que necesiten interactuar y pintarse azul.
Sí, realmente, alguien implementó una máquina virtual completa e intérprete usando macros CPP. Es intimidante
(EDITAR: prueba la versión archivada si el código de Rosetta también ha dejado de funcionar para ti).
Con la ayuda de las respuestas aquí (y estudiando P99 , Caos , orden y capa ) creo que tengo una prueba de concepto razonablemente simple y compacta (1). Como solo quería una funcionalidad de "repetición" en lugar de un intérprete completo, opté por un enfoque un tanto diferente al de esas otras soluciones. En lugar de crear macros genéricas "if", "while" o "when", utilicé directamente una serie de macros "decrement" que se expanden a la macro deseada más una llamada a la macro para n-1.
#ifndef _REPEAT_H
#define _REPEAT_H
// Usage: REPEAT_ADD_ONE(macro, times, start_n, macro_args... )
// Recursion allowed if inner macros use REPEAT_ADD_ONE_INNER().
// This demo header only allows 3 layers of recursion and max n=10.
// Sample code at bottom.
#define REPEAT_ADD_ONE(macro, times, start_n, macro_args... ) /
_REPEAT_EXPAND_3(REPEAT_ADD_ONE_INNER(macro, times, start_n, ## macro_args))
#define REPEAT_ADD_ONE_INNER(macro, times, start_n, macro_args... ) /
_REPEAT_ ## times(macro, start_n, _REPEAT_ADD_ONE, ## macro_args)
#define _REPEAT_0(args...) /* empty */
#define _REPEAT_1(macro, n, func, args...) _REPEAT_DEFER(macro)(n, ## args)
#define _REPEAT_2(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_1(m, f(n), f, ## a)
#define _REPEAT_3(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_2(m, f(n), f, ## a)
#define _REPEAT_4(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_3(m, f(n), f, ## a)
#define _REPEAT_5(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_4(m, f(n), f, ## a)
#define _REPEAT_6(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_5(m, f(n), f, ## a)
#define _REPEAT_7(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_6(m, f(n), f, ## a)
#define _REPEAT_8(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_7(m, f(n), f, ## a)
#define _REPEAT_9(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_8(m, f(n), f, ## a)
#define _REPEAT_10(m, n, f, a...) _REPEAT_DEFER(m)(n, ## a); _REPEAT_9(m, f(n), f, ## a)
// ...
#define _REPEAT_ADD_ONE(n, ignore...) _REPEAT_ADD_ONE_ ## n
#define _REPEAT_ADD_ONE_0 1
#define _REPEAT_ADD_ONE_1 2
#define _REPEAT_ADD_ONE_2 3
#define _REPEAT_ADD_ONE_3 4
#define _REPEAT_ADD_ONE_4 5
#define _REPEAT_ADD_ONE_5 6
#define _REPEAT_ADD_ONE_6 7
#define _REPEAT_ADD_ONE_7 8
#define _REPEAT_ADD_ONE_8 9
#define _REPEAT_ADD_ONE_9 10
#define _REPEAT_ADD_ONE_10 11
// ...
#define _REPEAT_EMPTY()
#define _REPEAT_DEFER(token) token _REPEAT_EMPTY()
#define _REPEAT_EXPAND_3(args...) _REPEAT_EXPAND(_REPEAT_EXPAND(_REPEAT_EXPAND(args)))
#define _REPEAT_EXPAND(args...) args
// ...
#endif // _REPEAT_H
#ifdef SAMPLE_CODE
// to generate code: cpp -DSAMPLE_CODE sample.c
// or easier to read: cpp -DSAMPLE_CODE sample.c > out.c; astyle out.c; less out.c
// to compile and run: gcc -Wall -O3 -DSAMPLE_CODE sample.c -o sample
int printf(const char *format, ...);
#define BODY(i) printf("%d/n", i);
void simple(void) {
REPEAT_ADD_ONE(BODY, 5, 1);
}
#define INNER(k, j, i) /
printf("(%d, %d, %d)/n", i, j, k); /
if (i == j && j == k) printf("Match!/n")
#define MIDDLE(j, i) REPEAT_ADD_ONE_INNER(INNER, 2, 2, j, i)
#define OUTER(i) REPEAT_ADD_ONE_INNER(MIDDLE, 3, 0, i)
void recursive(void) {
REPEAT_ADD_ONE(OUTER, 2, 1);
}
int main() {
simple();
recursive();
return 0;
}
#endif // SAMPLE_CODE
Todavía me cuesta entender muchas de las sutilezas, pero como otros señalaron, la regla general es que ninguna macro puede expandirse. La forma de evitar esto es crear una macro que se expanda justo al punto en el que se llamaría a sí misma, y luego colocar una envoltura alrededor de este resultado para completar la expansión.
El truco (común) que terminé usando es aprovechar el hecho de que una macro de tipo función solo se expande si es seguida inmediatamente por paréntesis. Se puede usar una macro "aplazar" que pone una ficha "vacía" entre el nombre de la macro llamada y sus paréntesis, y luego "expandir" esto como el argumento a otra macro.
Como la expansión de los argumentos tiene lugar en un contexto diferente al de la expansión inicial, la macro inicial se expandirá nuevamente. En mi solución, un nivel de expansión es necesario para cada nivel de recursión potencial. Si juegas con el código para entenderlo, puede ser útil disminuir el número de expansiones para verificar los resultados intermedios.
¡Gracias por toda la ayuda!
(1) Es cierto que el estándar para "razonablemente simple" es bastante flexible cuando se aplica a las macros de preprocesador recursivo. Sin embargo, es bastante compacto.
No estoy seguro de seguir todas tus macros allí. Esta respuesta aquí (también aquí también) explica cómo crear una macro REPEAT
propósito general, como esta:
#define REPEAT(count, macro, ...) /
WHEN(count) /
( /
OBSTRUCT(REPEAT_INDIRECT) () /
( /
DEC(count), macro, __VA_ARGS__ /
) /
OBSTRUCT(macro) /
( /
DEC(count), __VA_ARGS__ /
) /
)
#define REPEAT_INDIRECT() REPEAT
Esto requiere una cuenta, una macro y datos de usuario. Dado que la macro pasada es diferida, la macro REPEAT
puede volver a llamar directa y recursivamente. Así que aquí están sus macros de repetición OUTER
e INNER
:
#define OUTER(i, j) { REPEAT(j, INNER, i) }
#define INNER(j, i) if (j == INC(i)) printf("Match/n");
EVAL(REPEAT(2, OUTER, 3))
Esto dará como resultado esto:
{
if (0 == 1) printf("Match/n");
if (1 == 1) printf("Match/n");
if (2 == 1) printf("Match/n");
}
{
if (0 == 2) printf("Match/n");
if (1 == 2) printf("Match/n");
if (2 == 2) printf("Match/n");
}
Con suerte, esto tiene sentido.