C i C ++ (zaktualizowana odpowiedź)
Jak zauważono w komentarzu, moje oryginalne rozwiązanie miało dwa problemy:
- Parametry opcjonalne są dostępne tylko w C99 i późniejszych standardach rodziny języków.
- Końcowy przecinek w definicji wyliczenia jest również specyficzny dla C99 i późniejszych.
Ponieważ chciałem, aby mój kod był tak ogólny, jak to tylko możliwe, do pracy na starszych platformach, postanowiłem spróbować go w inny sposób. Jest dłuższy niż wcześniej, ale działa na kompilatorach i preprocesorach ustawionych na tryb zgodności C89 / C90. Wszystkie makra przekazują odpowiednią liczbę argumentów w kodzie źródłowym, choć czasami te makra „rozwijają się” w nicość.
Visual C ++ 2013 (aka wersja 12) emituje ostrzeżenia o brakujących parametrach, ale ani mcpp (preprocesor open source, który twierdzi, że jest zgodny ze standardem), ani gcc 4.8.1 (z przełącznikami -std = iso9899: 1990 -antantic-error) ostrzeżenia lub błędy dla tych wywołań makr ze skutecznie pustą listą argumentów.
Po przejrzeniu odpowiedniej normy (ANSI / ISO 9899-1990, 6.8.3, Makropolecenie) uważam, że istnieje wystarczająca dwuznaczność, że nie należy tego uważać za niestandardowy. „Liczba argumentów w wywołaniu makra podobnego do funkcji musi być zgodna z liczbą parametrów w definicji makra ...”. Wydaje się, że nie wyklucza pustej listy argumentów, dopóki potrzebne są nawiasy (i przecinki w przypadku wielu parametrów) do wywołania makra
Jeśli chodzi o problem z przecinkiem końcowym, ten problem rozwiązuje się poprzez dodanie dodatkowego identyfikatora do wyliczenia (w moim przypadku MMMM, który wydaje się równie rozsądny, jak cokolwiek, aby identyfikator podążał za 3999, nawet jeśli nie przestrzega przyjętych reguł sekwencjonowania cyfr rzymskich dokładnie).
Nieco czystsze rozwiązanie wymagałoby przeniesienia wyliczenia i obsługi makr do osobnego pliku nagłówka, co sugerowano w komentarzu w innym miejscu, i użycie undef nazw makr natychmiast po ich użyciu, aby uniknąć zanieczyszczenia przestrzeni nazw. Bez wątpienia należy również wybrać lepsze nazwy makr, ale jest to wystarczające do danego zadania.
Moje zaktualizowane rozwiązanie, a następnie moje oryginalne rozwiązanie:
#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;
}
Oryginalna odpowiedź (która otrzymała pierwsze sześć głosów pozytywnych, więc jeśli nikt nigdy więcej nie głosuje ponownie, nie powinieneś myśleć, że moje zaktualizowane rozwiązanie uzyskało pozytywne głosy):
W tym samym duchu, co wcześniejsza odpowiedź, ale wykonane w sposób, który powinien być przenośny przy użyciu tylko określonych zachowań (chociaż różne środowiska nie zawsze zgadzają się co do niektórych aspektów preprocesora). Traktuje niektóre parametry jako opcjonalne, ignoruje inne, powinien działać na preprocesorach, które nie obsługują __VA_ARGS__
makra, w tym C ++, używa makr pośrednich, aby zapewnić, że parametry zostaną rozszerzone przed wklejeniem tokena, a na koniec jest krótszy i myślę, że łatwiej go odczytać ( choć nadal jest trudny i prawdopodobnie niełatwy do odczytania, po prostu łatwiejszy):
#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 };