C & C ++ (resposta atualizada)
Como observado em um comentário, minha solução original teve dois problemas:
- Parâmetros opcionais estão disponíveis apenas nos padrões C99 e posteriores da família de idiomas.
- A vírgula à direita na definição de enum também é específica para C99 e posterior.
Como eu queria que meu código fosse o mais genérico possível para trabalhar em plataformas mais antigas, decidi fazer outra tentativa. É mais longo do que era antes, mas funciona em compiladores e pré-processadores configurados no modo de compatibilidade C89 / C90. Todas as macros recebem um número apropriado de argumentos no código-fonte, embora às vezes essas macros "se expandam" para nada.
O Visual C ++ 2013 (versão 12) emite avisos sobre parâmetros ausentes, mas nem o mcpp (um pré-processador de código aberto que reivindica alta conformidade com o padrão) nem o gcc 4.8.1 (com -std = iso9899: 1990 -pedantic-errors switches) emitem avisos ou erros para essas chamadas de macro com uma lista de argumentos efetivamente vazia.
Depois de revisar o padrão relevante (ANSI / ISO 9899-1990, 6.8.3, Substituição de macro), acho que há ambiguidade suficiente para que isso não deva ser considerado fora do padrão. "O número de argumentos na invocação de uma macro do tipo função deve concordar com o número de parâmetros na definição da macro ...". Parece não impedir uma lista de argumentos vazia, desde que os parênteses necessários (e vírgulas no caso de vários parâmetros) estejam disponíveis para chamar a macro
Quanto ao problema de vírgula à direita, isso é resolvido adicionando um identificador extra à enumeração (no meu caso, MMMM, que parece tão razoável quanto qualquer outra coisa para o identificador seguir 3999, mesmo que não obedeça às regras aceitas do seqüenciamento de números romanos exatamente).
Uma solução um pouco mais limpa envolveria mover a enum e dar suporte a macros para um arquivo de cabeçalho separado, como foi implícito em um comentário em outro lugar, e usar undef dos nomes de macro imediatamente após serem usados, para evitar poluir o espaço para nome. Sem dúvida, nomes de macro melhores devem ser escolhidos também, mas isso é adequado para a tarefa em questão.
Minha solução atualizada, seguida pela minha solução 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;
}
A resposta original (que recebeu as seis primeiras votações anteriores, portanto, se ninguém mais votá-las novamente, não pense que minha solução atualizada recebeu as votações anteriores):
No mesmo espírito de uma resposta anterior, mas feita de uma maneira que deveria ser portátil usando apenas um comportamento definido (embora ambientes diferentes nem sempre concordem em alguns aspectos do pré-processador). Trata alguns parâmetros como opcionais, ignora outros, deve funcionar em pré-processadores que não suportam a __VA_ARGS__
macro, incluindo C ++, usa macros indiretas para garantir que os parâmetros sejam expandidos antes da colagem do token e, finalmente, é mais curto e acho mais fácil de ler ( embora ainda seja complicado e provavelmente não seja fácil de ler, apenas mais 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 };