C & C ++ (обновленный ответ)
Как отмечено в комментарии, у моего первоначального решения было две проблемы:
- Дополнительные параметры доступны только в C99 и более поздних стандартах языкового семейства.
- Конечная запятая в определении enum также относится к C99 и более поздним версиям.
Поскольку я хотел, чтобы мой код был как можно более универсальным для работы на старых платформах, я решил сделать еще один удар. Это длиннее, чем было раньше, но оно работает на компиляторах и препроцессорах, настроенных на режим совместимости C89 / C90. Всем макросам передается соответствующее количество аргументов в исходном коде, хотя иногда эти макросы «расширяются» в ничто.
Visual C ++ 2013 (версия 12) выдает предупреждения о пропущенных параметрах, но ни mcpp (препроцессор с открытым исходным кодом, который заявляет о высоком соответствии стандарту), ни gcc 4.8.1 (с переключателями -std = iso9899: 1990 -pedantic-errors) не генерируют предупреждения или ошибки для этих вызовов макросов с фактически пустым списком аргументов.
После рассмотрения соответствующего стандарта (ANSI / ISO 9899-1990, 6.8.3, Замена макросов), я думаю, что существует достаточно двусмысленности, что это не следует считать нестандартным. «Число аргументов в вызове функционально-подобного макроса должно совпадать с количеством параметров в определении макроса ...». Кажется, он не исключает пустой список аргументов, если для вызова макроса необходимы круглые скобки (и запятые в случае нескольких параметров).
Что касается запаздывающей запятой, это решается добавлением дополнительного идентификатора к перечислению (в моем случае MMMM, который кажется таким же разумным, как что-либо для идентификатора, чтобы следовать 3999, даже если он не подчиняется принятым правилам римской последовательности чисел в точку).
Немного более чистое решение будет включать перемещение перечисления и поддерживающих макросов в отдельный заголовочный файл, как это подразумевается в комментарии в другом месте, и использование undef имен макросов сразу после их использования, чтобы избежать загрязнения пространства имен. Несомненно, следует выбирать и лучшие имена макросов, но этого вполне достаточно для поставленной задачи.
Мое обновленное решение, а затем мое оригинальное решение:
#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;
}
Исходный ответ (который получил первые шесть голосов «против», так что, если никто больше не проголосует за это снова, вы не должны думать, что мое обновленное решение получило отзывы «против»):
В том же духе, что и в предыдущем ответе, но сделано так, чтобы оно было переносимым с использованием только определенного поведения (хотя разные среды не всегда соглашаются по некоторым аспектам препроцессора). Рассматривает некоторые параметры как необязательные, игнорирует другие, он должен работать на препроцессорах, которые не поддерживают __VA_ARGS__
макрос, включая C ++, он использует косвенные макросы для обеспечения расширения параметров перед вставкой токена, и, наконец, он короче, и я думаю, что его легче читать ( хотя это все еще сложно и, вероятно, не легко читать, просто проще)
#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 };