在玩__VA_OPT__(,)
的时候,我注意到了以下行为.
背景
首先,请注意,C在初始化式中使用尾随逗号是很好的.它们是等效的:
int foo[] = { 10, 20, 30 };
int baz[] = { 10, 20, 30, };
因此,让我们设计一个初始化器宏,它也可以工作:
#define bar(...) { __VA_ARGS__ }
int foo[] = bar(10, 20, 30);
int baz[] = bar(10, 20, 30,);
现在我想把40
加到名单上,即使名单是空的.这两种方法都有效;foo
得到10, 20, 30, 40
,baz
得到40
:
#define bar(...) { __VA_ARGS__ __VA_OPT__(,) 40 }
int foo[] = bar(10, 20, 30);
int baz[] = bar();
问题
但是如果我们在结尾处忘记了一个额外的逗号呢?
#define bar(...) { __VA_ARGS__ __VA_OPT__(,) 40 }
int foo[] = bar(10, 20, 30, );
现在我们得到了这个错误,它不是超直观的.它从不直接指向30
后面的"问题逗号":
r.c: In function ‘main’:
r.c:160:43: error: expected expression before ‘,’ token
160 | #define bar(...) { __VA_ARGS__ __VA_OPT__(,) 40 }
| ^
r.c:164:21: note: in expansion of macro ‘bar’
164 | int foo[] = bar(10, 20, 30,);
| ^~~
其中,预处理器将逗号加倍:
# cpp r.c|grep -v ^#|indent
...
int foo[] = { 10, 20, 30,, 40 };
问题
有没有可能设计一个宏来防止逗号加倍,或者调整宏以从编译器中产生更合理的错误?
More complicated example
上面的例子非常简单,但是当事情变得复杂时,并且您遗漏了一个逗号,那么错误就很可怕了.以here为例.
(Please note:这不是XY problem个问题,因为I really do want to know if C macros can suppress double-comma situations.请不要对这个更复杂但仍是人为设计的第二个例子提出"修复"建议.)
假设我们正在静态初始化递归的树状 struct :
struct menu
{
char *name;
struct menu **submenu;
};
#define MENU_LIST(...) (struct menu*[]){ __VA_ARGS__ __VA_OPT__(,) NULL }
#define MENU_ITEM(...) &(struct menu){ __VA_ARGS__ }
它提供了非常好的函数式语法,如下所示...但是你能在下面的代码中发现"额外的"逗号吗? 如果你习惯了尾随逗号是有效的,你可能看不到它:
r.c:11:65: error: expected expression before ‘,’ token
11 | #define MENU_LIST(...) (struct menu*[]){ __VA_ARGS__ __VA_OPT__(,) NULL }
| ^
r.c:113:25: note: in expansion of macro ‘MENU_LIST’
113 | struct menu *mymenu[] = MENU_LIST(
| ^~~~~~~~~
r.c:122:9: note: in expansion of macro ‘MENU_ITEM’
122 | MENU_ITEM(
| ^~~~~~~~~
r.c:124:36: note: in expansion of macro ‘MENU_LIST’
124 | .submenu = MENU_LIST(
| ^~~~~~
struct menu *mymenu[] = MENU_LIST( // line 113
MENU_ITEM(
.name = "planets",
.submenu = MENU_LIST(
MENU_ITEM( .name = "Earth" ),
MENU_ITEM( .name = "Mars" ),
MENU_ITEM( .name = "Jupiter" )
)
),
MENU_ITEM( // line 122
.name = "stars",
.submenu = MENU_LIST( // line 124
MENU_ITEM( .name = "Sun" ),
MENU_ITEM( .name = "Vega" ),
MENU_ITEM( .name = "Proxima Centauri" ),
)
),
MENU_ITEM(
.name = "satellites",
.submenu = MENU_LIST(
MENU_ITEM( .name = "ISS" ),
MENU_ITEM( .name = "OreSat0" )
)
)
);