¿Podemos tener macros recursivas?

9 minutos de lectura

¿Podemos tener macros recursivas
usuario1367292

Quiero saber si podemos tener macros recursivas en C/C++. En caso afirmativo, proporcione un ejemplo de ejemplo.

Segunda cosa: ¿por qué no puedo ejecutar el siguiente código? ¿Cuál es el error que estoy haciendo? ¿Es por las macros recursivas?

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

  • Las macros de C son macros de texto. Si las macros fueran recursivas, SIEMPRE construirías una expresión infinita porque las macros no pueden hacer literalmente nada más que ‘reemplazar esta con que

    – Cúbico

    16 de septiembre de 2012 a las 14:19

  • @Cubic: En realidad, las macros pueden hacer mucho más. Citación de parámetros, concatenación de texto y reemplazo iterativo de macros definidas posteriormente. Pero no la recursividad.

    – Martín York

    16 de septiembre de 2012 a las 15:16


  • No estoy seguro POR QUÉ te gustaría hacer esto. si tiene la intención de realizar un cálculo recursivo en el momento de la compilación, es posible que le interesen las plantillas variádicas (una nueva característica del nuevo estándar C++).

    – Alejandro Ah

    8 de junio de 2013 a las 22:14

  • no, pero las plantillas, por otro lado, son Turing complete.stackoverflow.com/questions/189172/c-templates-turing-complete

    – Jasén

    7 de mayo de 2019 a las 2:26

1646956688 403 ¿Podemos tener macros recursivas
Pablo Fultz II

Las macros no se expanden directamente de forma recursiva, pero existen soluciones alternativas. Cuando el preprocesador escanea y se expande pr(5):

pr(5)
^

crea un contexto incapacitante, de modo que cuando ve pr otra vez:

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

se pinta de azul y ya no puede expandirse, sin importar lo que intentemos. Pero podemos evitar que nuestra macro se pinte de azul usando expresiones diferidas y algo de direccionamiento indirecto:

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

Así que ahora se expandirá así:

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

Lo cual es perfecto, porque pr nunca se pintó de azul. Solo necesitamos aplicar otro escaneo para que se expanda aún más:

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

Podemos aplicar dos escaneos para que se expanda aún más:

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

Sin embargo, dado que no existe una condición de terminación, nunca podemos aplicar suficientes escaneos. No estoy seguro de lo que quiere lograr, pero si tiene curiosidad sobre cómo crear macros recursivas, aquí hay un ejemplo de cómo crear una macro de repetición recursiva.

Primero una macro para aplicar muchos escaneos:

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

A continuación, una macro concat que es útil para la coincidencia de patrones:

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

Contadores de incremento y decremento:

#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

Algunas macros útiles para condicionales:

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

Poniendo todo junto podemos crear una macro de repetición:

#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

Entonces, sí, con algunas soluciones puede tener macros recursivas en C/C++.

  • Intentando esto en gcc 4.8.3 con -std=c99 da error para la linea OBSTRUCT(REPEAT_INDIRECT) (): error: 'REPEAT_INDIRECT' undeclared here (not in a function) . Mover la definición de REPEAT_INDIRECT hasta antes de REPEAT no soluciona.

    –MM

    18 de junio de 2014 a las 5:14

  • ¿Cuál es la salida del preprocesador?

    –Paul Fultz II

    20 de junio de 2014 a las 0:16

  • La macro OBSTRUCT no se expande aquí porque no está definida aquí. @Paul lo define en su publicación de blog original.

    -Austin Mullins

    27/10/2015 a las 18:05

  • Solución elegante. Esto en realidad es cálculo lamda.

    – yangwenjin

    12 abr 2021 a las 10:40

  • Gran solución, pero no puedo hacer que esto funcione en VS, parece que EAT no funciona y siempre deja la última iteración de REPEAT(0, macro) alrededor.

    – Alexander Torstling

    10 de julio de 2021 a las 8:12

¿Podemos tener macros recursivas
verdesmarald

Su compilador probablemente proporcione una opción para solo preprocesar, no compilar en realidad. Esto es útil si está tratando de encontrar un problema en una macro. Por ejemplo usando 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();
}

Como puede ver, no es recursivo. pr(x) solo se reemplaza una vez durante el preprocesamiento. Lo importante a recordar es que todo lo que hace el preprocesador es reemplazar ciegamente una cadena de texto con otra, en realidad no evalúa expresiones como (x == 1).

La razón por la que su código no se compilará es que pr(5 -1) no fue reemplazado por el preprocesador, por lo que termina en la fuente como una llamada a una función indefinida.

  • ¿Por qué pr(5-1) se trata como una llamada de función indefinida? He definido una macro, por lo que debería expandirse aún más a: ((5-1==1)? 1: pr(5-1-1)) ….

    – usuario1367292

    16 de septiembre de 2012 a las 14:25

  • @ user1367292 No, no puede tener macros recursivas. Si en realidad siguiera reemplazando pr(x) con pr(x-1) simplemente se repetiría infinitamente pr(x-1), pr(x-1-1), pr(x-1-1-1)etc…

    – verdesmarald

    16 de septiembre de 2012 a las 14:33


  • veredesmarald — Entonces, ¿quieres decir “No podemos tener macros recursivas?”. Además… ¿hay alguna solución disponible para lograrlo?

    – usuario1367292

    16/09/2012 a las 14:35

  • @ user1367292 No. No puedes. Lo que está proponiendo no tiene sentido en el contexto del preprocesador. ¿Cómo llegarías a un caso base para tu recursividad cuando todo lo que estás haciendo es reemplazar una cadena consigo misma + algunas otras cosas una y otra vez?

    – verdesmarald

    16 de septiembre de 2012 a las 14:37


  • veredesmarald — Gracias 🙂 Lo tengo.

    – usuario1367292

    16 de septiembre de 2012 a las 14:38

Tu no eres supuesto tener macros recursivas en C o C++.

El lenguaje relevante del estándar C++, sección 16.3.4 párrafo 2:

Si el nombre de la macro que se reemplaza se encuentra durante este escaneo de la lista de reemplazo (sin incluir el resto de los tokens de preprocesamiento del archivo de origen), no se reemplaza. Además, si algún reemplazo anidado encuentra el nombre de la macro que se reemplaza, no se reemplaza. Estos tokens de preprocesamiento de nombre de macro no reemplazados ya no están disponibles para un reemplazo posterior, incluso si se (re)examinan más tarde en contextos en los que ese token de preprocesamiento de nombre de macro se habría reemplazado de otro modo.

Hay cierto margen de maniobra en este idioma. Con múltiples macros que se invocan entre sí, hay un área gris donde esa redacción no dice exactamente qué se debe hacer. Hay un problema activo contra el estándar de C++ con respecto a este problema del abogado del lenguaje; ver http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#268 .

Ignorando el problema del abogado del lenguaje, todos los proveedores de compiladores entienden la intención:

Las macros recursivas no están permitidas en C o en C++.

1646956689 148 ¿Podemos tener macros recursivas
Zdeslav Vojkovic

Lo más probable es que no pueda ejecutarlo porque no puede compilarlo. Además, si se compilara correctamente, siempre devolvería 1. ¿Quiso decir (n==1)? 1 : n * pr(n-1).

Las macros no pueden ser recursivas. De acuerdo con el capítulo 16.3.4.2 (gracias Loki Astari), si la macro actual se encuentra en la lista de reemplazo, se deja como está, por lo tanto, su pr en la definición no se cambiará:

Si el nombre de la macro que se reemplaza se encuentra durante este escaneo de la lista de reemplazo (sin incluir el resto de los tokens de procesamiento previo del archivo de origen), no se reemplaza. Además, si algún reemplazo anidado encuentra el nombre de la macro que se reemplaza, no se reemplaza. Estos tokens de preprocesamiento de nombre de macro no reemplazados ya no están disponibles para un reemplazo posterior, incluso si se (re)examinan más tarde en contextos en los que ese token de preprocesamiento de nombre de macro se habría reemplazado de otro modo.

Tu llamada:

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

fue convertido por preprocesador en:

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

Durante esto, la definición de pr la macro está ‘perdida’ y el compilador muestra un error como “‘pr’ no se declaró en este ámbito (de hecho)” porque no hay una función nombrada pr.

No se recomienda el uso de macros en C++. ¿Por qué no simplemente escribes una función?

En este caso, incluso podría escribir una función de plantilla para que se resuelva en tiempo de compilación y se comporte como un valor constante:

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

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

No puede tener macros recursivas en C o C++.

  • De acuerdo … Tengo mi primera duda clara de que no puedes tener macros recursivas. ¿Qué pasa con el error en mi código de muestra en la pregunta…???

    – usuario1367292

    16 de septiembre de 2012 a las 14:19

  • No dices qué error obtienes, pero el pr se utiliza recursivamente en el pr macro no se expandirá, y probablemente resulte en un error de “función indefinida” o algo así.

    – algo

    16 de septiembre de 2012 a las 14:20


  • De acuerdo … Tengo mi primera duda clara de que no puedes tener macros recursivas. ¿Qué pasa con el error en mi código de muestra en la pregunta…???

    – usuario1367292

    16 de septiembre de 2012 a las 14:19

  • No dices qué error obtienes, pero el pr se utiliza recursivamente en el pr macro no se expandirá, y probablemente resulte en un error de “función indefinida” o algo así.

    – algo

    16 de septiembre de 2012 a las 14:20


¿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