C y C ++ (respuesta actualizada)
Como se observó en un comentario, mi solución original tenía dos problemas:
- Los parámetros opcionales solo están disponibles en C99 y estándares posteriores de la familia de idiomas.
- La coma final en la definición de enumeración también es específica de C99 y posterior.
Como quería que mi código fuera lo más genérico posible para trabajar en plataformas más antiguas, decidí darle otra puñalada. Es más largo que antes, pero funciona en compiladores y preprocesadores configurados en modo de compatibilidad C89 / C90. Todas las macros pasan un número apropiado de argumentos en el código fuente, aunque a veces esas macros se "expanden" en nada.
Visual C ++ 2013 (también conocido como versión 12) emite advertencias sobre parámetros faltantes, pero ni mcpp (un preprocesador de código abierto que afirma un alto cumplimiento del estándar) ni gcc 4.8.1 (con -std = iso9899: 1990 -conmutadores de errores pedagógicos) emiten advertencias o errores para esas invocaciones macro con una lista de argumentos efectivamente vacía.
Después de revisar el estándar relevante (ANSI / ISO 9899-1990, 6.8.3, Reemplazo de macros), creo que hay suficiente ambigüedad para que esto no se considere no estándar. "El número de argumentos en una invocación de una macro similar a una función debe coincidir con el número de parámetros en la definición de macro ...". No parece excluir una lista de argumentos vacía siempre que los paréntesis necesarios (y las comas en el caso de múltiples parámetros) estén en su lugar para invocar la macro
En cuanto al problema de coma final, eso se resuelve agregando un identificador adicional a la enumeración (en mi caso, MMMM que parece tan razonable como cualquier cosa para que el identificador siga 3999 incluso si no obedece las reglas aceptadas de secuenciación de números romanos exactamente).
Una solución ligeramente más limpia implicaría mover la enumeración y admitir macros a un archivo de encabezado separado, como se implica en un comentario en otro lugar, y usar undef de los nombres de macro inmediatamente después de que se usaron para evitar contaminar el espacio de nombres. Indudablemente, también deberían elegirse mejores nombres de macro, pero esto es adecuado para la tarea en cuestión.
Mi solución actualizada, seguida de mi solución original:
#define _0(i,v,x)
#define _1(i,v,x) i
#define _2(i,v,x) i##i
#define _3(i,v,x) i##i##i
#define _4(i,v,x) i##v
#define _5(i,v,x) v
#define _6(i,v,x) v##i
#define _7(i,v,x) v##i##i
#define _8(i,v,x) v##i##i##i
#define _9(i,v,x) i##x
#define k(p,s) p##s,
#define j(p,s) k(p,s)
#define i(p) j(p,_0(I,V,X)) j(p,_1(I,V,X)) j(p,_2(I,V,X)) j(p,_3(I,V,X)) j(p,_4(I,V,X)) j(p,_5(I,V,X)) j(p,_6(I,V,X)) j(p,_7(I,V,X)) j(p,_8(I,V,X)) j(p,_9(I,V,X))
#define h(p,s) i(p##s)
#define g(p,s) h(p,s)
#define f(p) g(p,_0(X,L,C)) g(p,_1(X,L,C)) g(p,_2(X,L,C)) g(p,_3(X,L,C)) g(p,_4(X,L,C)) g(p,_5(X,L,C)) g(p,_6(X,L,C)) g(p,_7(X,L,C)) g(p,_8(X,L,C)) g(p,_9(X,L,C))
#define e(p,s) f(p##s)
#define d(p,s) e(p,s)
#define c(p) d(p,_0(C,D,M)) d(p,_1(C,D,M)) d(p,_2(C,D,M)) d(p,_3(C,D,M)) d(p,_4(C,D,M)) d(p,_5(C,D,M)) d(p,_6(C,D,M)) d(p,_7(C,D,M)) d(p,_8(C,D,M)) d(p,_9(C,D,M))
#define b(p) c(p)
#define a() b(_0(M,N,O)) b(_1(M,N,O)) b(_2(M,N,O)) b(_3(M,N,O))
enum { _ a() MMMM };
#include <stdio.h>
int main(int argc, char** argv)
{
printf("%d", MMMCMXCIX * MMMCMXCIX);
return 0;
}
La respuesta original (que recibió los primeros seis votos a favor, por lo que si nadie vuelve a votar esto, no debería pensar que mi solución actualizada obtuvo los votos a favor):
En el mismo espíritu que una respuesta anterior, pero de una manera que debería ser portátil utilizando solo un comportamiento definido (aunque los diferentes entornos no siempre coinciden en algunos aspectos del preprocesador). Trata algunos parámetros como opcionales, ignora otros, debería funcionar en preprocesadores que no admitan la __VA_ARGS__
macro, incluido C ++, utiliza macros indirectos para garantizar que los parámetros se expandan antes de pegar el token, y finalmente es más corto y creo que es más fácil de leer ( aunque todavía es complicado y probablemente no sea fácil de leer, solo que más fácil):
#define g(_,__) _, _##I, _##II, _##III, _##IV, _##V, _##VI, _##VII, _##VIII, _##IX,
#define f(_,__) g(_,)
#define e(_,__) f(_,) f(_##X,) f(_##XX,) f(_##XXX,) f(_##XL,) f(_##L,) f(_##LX,) f(_##LXX,) f(_##LXXX,) f(_##XC,)
#define d(_,__) e(_,)
#define c(_,__) d(_,) d(_##C,) d(_##CC,) d(_##CCC,) d(_##CD,) d(_##D,) d(_##DC,) d(_##DCC,) d(_##DCCC,) d(_##CM,)
#define b(_,__) c(_,)
#define a b(,) b(M,) b(MM,) b(MMM,)
enum { _ a };