C & C ++ (оновлений відповідь)
Як помічено в коментарі, у мого оригінального рішення було дві проблеми:
- Необов’язкові параметри доступні лише в C99 та пізніших стандартах мовної сім'ї.
- Кінцева кома у визначенні перерахунків характерна також для C99 та пізніших версій.
Оскільки я хотів, щоб мій код був максимально загальним для роботи на старих платформах, я вирішив зробити інший замах на нього. Це довше, ніж було раніше, але він працює над компіляторами та препроцесорами, встановленими в режимі сумісності C89 / C90. Усім макросам передається відповідна кількість аргументів у вихідному коді, хоча іноді ці макроси "розширюються" у ніщо.
Visual C ++ 2013 (він же версія 12) не випромінює попередження про відсутні параметри, але не видає ні mcpp (препроцесор з відкритим кодом, який вимагає високої відповідності стандарту), ні gcc 4.8.1 (з -std = iso9899: 1990 -перемикачами-помилки) попередження або помилки для тих макро-викликів із фактично порожнім списком аргументів.
Переглянувши відповідний стандарт (ANSI / ISO 9899-1990, 6.8.3, Заміна макросів), я думаю, що існує достатня двозначність, що це не слід вважати нестандартним. "Кількість аргументів у виклику функціонального макросу повинна відповідати кількості параметрів у визначенні макросу ...". Схоже, це не виключає порожній список аргументів, поки потрібні дужки (і коми у випадку кількох параметрів) є для виклику макросу
Що стосується проблеми з комою в кінці, то це вирішується шляхом додавання до перерахунку додаткового ідентифікатора (в моєму випадку MMMM, який видається таким же розумним, як і все, для того, щоб ідентифікатор наслідував 3999, навіть якщо він не підкоряється прийнятим правилам послідовності римських цифр точно).
Трохи більш чисте рішення передбачає переміщення enum та підтримку макросів до окремого файлу заголовка, як це було передбачено в коментарі в іншому місці, та використання 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 };