C & C ++ (aktualisierte Antwort)
Wie in einem Kommentar festgestellt, hatte meine ursprüngliche Lösung zwei Probleme:
- Optionale Parameter sind nur in C99 und späteren Standards der Sprachfamilie verfügbar.
- Das nachfolgende Komma in der Enum-Definition gilt auch für C99 und höher.
Da ich wollte, dass mein Code so allgemein wie möglich ist, um auf älteren Plattformen zu arbeiten, beschloss ich, es noch einmal zu versuchen. Es ist länger als zuvor, funktioniert jedoch auf Compilern und Präprozessoren, die auf den Kompatibilitätsmodus C89 / C90 eingestellt sind. Allen Makros wird eine angemessene Anzahl von Argumenten im Quellcode übergeben, obwohl diese Makros manchmal zu nichts "expandieren".
Visual C ++ 2013 (auch bekannt als Version 12) gibt Warnungen über fehlende Parameter aus, aber weder mcpp (ein Open-Source-Präprozessor, der hohe Übereinstimmung mit dem Standard behauptet) noch gcc 4.8.1 (mit -std = iso9899: 1990 -pedantic-errors-Schaltern) Warnungen oder Fehler für diese Makroaufrufe mit einer effektiv leeren Argumentliste.
Nach Durchsicht der relevanten Norm (ANSI / ISO 9899-1990, 6.8.3, Macro Replacement) besteht meines Erachtens hinreichende Unklarheit, dass dies nicht als Nicht-Norm angesehen werden sollte. "Die Anzahl der Argumente in einem Aufruf eines funktionsähnlichen Makros muss mit der Anzahl der Parameter in der Makrodefinition übereinstimmen ...". Es scheint eine leere Argumentliste nicht auszuschließen, solange die erforderlichen Klammern (und Kommas bei mehreren Parametern) vorhanden sind, um das Makro aufzurufen
Das Problem mit dem nachstehenden Komma wird gelöst, indem der Aufzählung ein zusätzlicher Bezeichner hinzugefügt wird (in meinem Fall MMMM, der für den Bezeichner so sinnvoll wie alles andere zu sein scheint, als 3999 zu folgen, auch wenn er nicht den anerkannten Regeln der römischen Ziffernfolge entspricht genau).
Eine etwas sauberere Lösung würde darin bestehen, die Aufzählung und die unterstützenden Makros in eine separate Header-Datei zu verschieben, wie in einem Kommentar an anderer Stelle impliziert, und die Undef der Makronamen unmittelbar nach ihrer Verwendung zu verwenden, um eine Verschmutzung des Namespace zu vermeiden. Zweifellos sollten auch bessere Makronamen gewählt werden, dies ist jedoch für die jeweilige Aufgabe angemessen.
Meine aktualisierte Lösung, gefolgt von meiner ursprünglichen Lösung:
#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;
}
Die ursprüngliche Antwort (die die ersten sechs Upvotes erhalten hat, falls dies also noch einmal von niemandem bewertet wird, sollten Sie nicht glauben, dass meine aktualisierte Lösung die Upvotes erhalten hat):
Im gleichen Sinne wie eine frühere Antwort, jedoch auf eine Weise, die nur mit definiertem Verhalten portierbar sein sollte (obwohl sich unterschiedliche Umgebungen in einigen Aspekten des Präprozessors nicht immer einig sind). Behandelt einige Parameter als optional, ignoriert andere, sollte auf Präprozessoren funktionieren, die das __VA_ARGS__
Makro nicht unterstützen , einschließlich C ++. Verwendet indirekte Makros, um sicherzustellen, dass die Parameter vor dem Einfügen des Tokens erweitert werden. es ist zwar immer noch schwierig und wahrscheinlich nicht leicht zu lesen, aber einfacher):
#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 };