¿Cómo escribo una macro de “repetición” for-loop recursiva para generar código C con el preprocesador CPP?

13 minutos de lectura

avatar de usuario
nathan kurz

Quiero obligar al preprocesador a que genere un código automático por mí. No necesito mucho: solo un bucle for simple que contiene otro bucle for.[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é se necesitan varias capas de macros para generar un nombre de función con el pegado de tokens. De hecho, tengo el bucle for funcionando. Pero cuando se trata de poner un bucle dentro de un bucle, estoy reducido a rociar al azar con DEFER, EVAL y OBSTRUCT y esperando lo mejor.

No me dejaré disuadir por 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, ni siquiera lo vea, sin las gafas de seguridad adecuadas. Haz como si quisieras que solo pregunto por interés teórico. O que mi única otra opción es usar M4: porque si las macros recursivas en CPP son pervertidas, 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 de tema, tiene un tono algo mezquino y combativo, y está muy por encima de mi cabeza. Pero creo que la respuesta que busco está ahí en alguna parte.

El siguiente mejor es la documentación para un encabezado que abusa de CPP llamado Cloak:
https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-trucos,-consejos-y-modismos

Se necesita un enfoque algo diferente para la iteración, y tal vez satisfaga mis necesidades en su lugar. Pero también es un buen resumen.

Aquí hay un código reducido para mostrar dónde estoy atascado.

repetir.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))

muestra.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 quiero. Pero ninguno está del todo 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á el resultado 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");
   };
}

Esencialmente, puedo hacer que las cosas funcionen si uso un bloque como el cuerpo del bucle externo, pero no si uso una macro similar a una función. Pero necesito usar una macro con argumentos para que los cuerpos del ciclo puedan usar el contador del ciclo 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 ser aplazar la expansión del bucle interior hasta que se haya creado el bucle exterior, y luego forzar otra pasada que amplíe el bucle interior. Pero por alguna razón, mi enfoque de “mono aleatorio” aún no ha producido una solución…

[1] Subestimación intencionada.

avatar de usuario
Leushenko

Vesa Karvonen “Ordenar” biblioteca/idioma definitivamente puede hacer esto por ti. Implementa recursividad y bucles sin restricciones en el preprocesador C, y como una bonificación realmente genial lo viste con la sintaxis agradable y concisa de un lenguaje de programación “adecuado” (para aclarar: este no es un preprocesador alternativo, solo hace un lote de token-pegar para mantener sus palabras clave cortas. Todavía es puro CPP).

Utiliza una técnica bastante diferente, convirtiendo sus metaprogramas a CPS y luego pasándolos a un soltero construcción de bucle 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 desee porque no tienen controladores separados que necesitan interactuar y pintarse de azul entre sí.

Sí, en serio, alguien implementó una máquina virtual completa y un intérprete usando macros CPP. Es intimidante.

(EDITAR: prueba la versión archivada si Rosetta Code ha dejado de funcionar para usted también).

avatar de usuario
nathan kurz

Con la ayuda de las respuestas aquí (y estudiando P99, Caos, Pedidoy Capa) Creo que tengo una prueba de concepto razonablemente simple y compacta (1). Como solo quería la funcionalidad de “repetición” en lugar de un intérprete completo, opté por un enfoque algo diferente al de esas otras soluciones. En lugar de crear macros genéricas “si”, “mientras” o “cuando”, utilicé directamente una serie de macros “decrecientes” 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 por sí misma. La forma de evitar esto es crear una macro que se expanda justo hasta el punto en que se llamaría a sí misma, y ​​luego colocar un envoltorio 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 va seguida inmediatamente de paréntesis. Se puede usar una macro “aplazada” que coloca un token “vacío” entre el nombre de la macro llamada y sus paréntesis, y luego “expandir” esto como argumento a otra macro.

Dado que 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, es necesario un nivel de expansión para cada nivel de recursividad 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) Cierto, el estándar para “razonablemente simple” es bastante flexible cuando se aplica a macros de preprocesador recursivas. Eso sí, es bastante compacto.

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.

  • Hola Jens — Se ve emocionante y definitivamente lo revisaré. ¡La documentación es genial! Parece que toma el enfoque basado en NARGS y realiza todo su procesamiento en argumentos variados. ¿Sabe de antemano si el bucle dentro de un bucle funciona y si es posible que la macro del bucle interior reciba el contador del bucle exterior como argumento?

    – Nathan Kurz

    16 de junio de 2013 a las 12:06


  • @NathanKurz, creo que anidar dos bucles que se basan en la misma primitiva no funcionaría fácilmente. Pero si para el caso de uso sería suficiente combinar P99_FOR con P99_SER (o similar) que debería ser posible. La única de estas construcciones que tiene algo así como un contador de bucle es P99_FoRpero debería ser posible hacerlo accesible a otra construcción iterativa interna.

    – Jens Gusted

    16 de junio de 2013 a las 14:27

  • Investigué P99, pero no quiero una dependencia externa, y es difícil comenzar a comprender qué se necesita para P99_FOR y qué se necesita solo para eso. Todo se nombra de manera muy confusa y al azar.

    – MarcusJ

    21 de noviembre de 2021 a las 7:03

  • @MarcusJ, lo siento si tienes esa impresión y los nombres te confunden. P99_FOR es altamente no trivial, si no fuera así, probablemente no lo habría proporcionado como un proyecto de código abierto en primer lugar, creo, pero simplemente publiqué un código en mi blog. Creo que implementar tal cosa por su cuenta generalmente no es una buena idea, consume mucho tiempo y es propenso a errores.

    – Jens Gusted

    22 de noviembre de 2021 a las 9:37

avatar de usuario
Pablo Fultz II

No estoy seguro de seguir todas tus macros allí. Esta respuesta aquí (también aquí ahora también) explica cómo crear un propósito general REPEAT macro, así:

#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 toma un conteo, una macro y datos de usuario. Dado que la macro pasada es diferida, el REPEAT macro se puede volver a llamar directa y recursivamente. Así que aquí está tu OUTER y INNER repetir macros:

#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 generará 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.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad