Varódico macros de preprocesador recursivo: ¿es posible?

Me encontré con un pequeño problema teórico. En una pieza de código que estoy manteniendo hay un conjunto de macros como

#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 

Lo que me gustaría hacer es reemplazarlos por algo como esto:

 /* 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__)) 

… que, por supuesto, no es un código de preprocesador válido.

Ignorando que este caso particular probablemente debería resolverse usando una función en lugar de una macro de preprocesador , ¿es posible definir una macro variada MAX_OF_N ()?

Solo para mayor claridad, el resultado final debe ser una sola macro que tome un número arbitrario de parámetros y evalúe al más grande de ellos. Tengo la extraña sensación de que esto debería ser posible, pero no veo cómo.

No, porque el preprocesador solo toma un “deslizamiento” en el archivo. No hay forma de que pueda definir macros recursivamente.

El único código que he visto hacer algo como esto no era vario, sino que usaba valores por defecto que el usuario tenía que pasar:

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

suponiendo que todos los valores fueran no negativos.

Las funciones en línea deberían darle lo mismo para C ++ al menos. Como dices, es mejor dejarlo en una función con argumentos variables similares a printf() .

Es posible escribir una macro que evalúa la cantidad de argumentos con los que se llama. (No pude encontrar un enlace al lugar donde lo vi por primera vez.) Así que podrías escribir MAX_OF_N () que funcionaría como quisieras, pero igual necesitarías todas las macros numeradas hasta cierto límite:

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

Ahora MAX_OF_N(a,b,c,d,e) evaluará a max(a, max(b, max(c, max(d, e)))) . (He probado en gcc 4.2.1.)

Tenga en cuenta que es fundamental que el caso base ( MAX_OF_2 ) no repita sus argumentos más de una vez en la expansión (por lo que pongo max en este ejemplo). De lo contrario, estaría duplicando la duración de la expansión para cada nivel, por lo que puede imaginar lo que sucederá con 64 argumentos 🙂

Puede considerar esta trampa, ya que no es recursiva y no hace el trabajo en el preprocesador. Y usa una extensión GCC. Y solo funciona para un tipo. Sin embargo, es una macro variada MAX_OF_N:

 #include  #include  #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); } 

Oh sí, y debido a la posible expresión variable en la lista de inicializadores, no creo que un equivalente de esto (usando su propia función para evitar std :: max_element) funcione en C89. Pero tampoco estoy seguro de que las macros variadas estén en C89.

Aquí hay algo que creo que supera la restricción del "único tipo". Aunque se está poniendo un poco peludo:

 #include  #include  #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); } 

Creo que, incluso si pudieras expandir las macros recursivamente, habría un pequeño problema con tu enfoque en términos de eficiencia … cuando las macros se expanden, si MAX_OF_[N-1] es mayor, entonces debes evaluar de nuevo desde cero.

Aquí hay una respuesta tonta y estúpida que probablemente a nadie le guste xD

archivo “fuente.c”

 #include "my_macros.h" ... 

archivo “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 

archivo “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)) 

entonces tendrás esas bonitas macros definidas:

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

y lo mejor de todo es que … funciona ^ _ ^

Si va por este camino en C ++, eche un vistazo a la metaprogtwigción de plantillas . No es lindo, y puede que no resuelva tu problema exacto, pero manejará la recursión.

Primero, las macros no se expanden recusivamente. Sin embargo, las macros pueden tener reentrada creando una macro para cada nivel de recursión y luego deduciendo el nivel de recursión. Sin embargo, toda esta repetición y deducir la recursión, está a cargo de la biblioteca Boost.Preprocessor . Por lo tanto, puede usar la macro de plegado de mayor orden para calcular el máximo:

 #define MAX_EACH(s, x, y) BOOST_PP_IF(BOOST_PP_GREATER_EQUAL(x, y), x, y) #define MAX(...) BOOST_PP_SEQ_FOLD_LEFT(MAX_EACH, 0, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) MAX(3, 6, 8) //Outputs 8 MAX(4, 5, 9, 2) //Outputs 9 

Ahora, esto comprenderá números literales entre 0-256. No funcionará en variables o expresiones C ++, porque el preprocesador C no comprende C ++. Es solo un reemplazo de texto puro. Pero C ++ proporciona una función llamada “función” que funcionará en expresiones C ++, y puede usarla para calcular el valor máximo.

 template T max(T x, T y) { return x > y ? x : y; } template auto max(X x, T ... args) -> decltype(max(x, max(args...))) { return max(x, max(args...)); } 

Ahora, el código anterior requiere un comstackdor C ++ 11. Si está utilizando C ++ 03, puede crear múltiples sobrecargas de la función para simular parámetros variados. Además, podemos usar el preprocesador para generar este código repetitivo para nosotros (para eso sirve). Entonces en C ++ 03, puedes escribir esto:

 template T max(T x, T y) { return x > y ? x : y; } #define MAX_FUNCTION(z, n, data) \ template \ T max(T x, BOOST_PP_ENUM_PARAMS(n, T x)) \ { \ return max(x, max(BOOST_PP_ENUM_PARAMS(n, x)));\ } BOOST_PP_REPEAT_FROM_TO(2, 64, MAX_FUNCTION, ~)