这是一种实际上可以满足您要求的方法...我已经将其预定义为与 MSVC、gcc 和 clang 一起使用(仅与 gcc 和 clang 或仅使用 MSVC 一起使用会更简单)。
这实现了OPTIONAL,它需要一个元组(带括号的标记)作为第一个参数。当 OPTIONAL 仅使用空的第二个参数调用时,它会扩展为空;否则,它将扩展为第一个参数的展开版本。最终结果是 C++20 的__VA_OPT__ 的一种模拟(但肯定不是等效)。
以下是OPTIONAL的实现,并支持宏:
#define GLUE(A,B) GLUE_C(GLUE_I,(A,B))
#define GLUE_C(A,B) A B
#define GLUE_I(A,B) A##B
#define FIRST(...) FIRST_C(FIRST_I,(__VA_ARGS__,))
#define FIRST_C(A,B) A B
#define FIRST_I(X,...) X
#define THIRD(...) THIRD_C(THIRD_CC,(THIRD_I,(__VA_ARGS__,,,)))
#define THIRD_C(A,B) A B
#define THIRD_CC(A,B) A B
#define THIRD_I(A,B,C,...) C
#define COUNT(...) COUNT_C(COUNT_I,(__VA_ARGS__,9,8,7,6,5,4,3,2,1,))
#define COUNT_C(A,B) A B
#define COUNT_I(_,_9,_8,_7,_6,_5,_4,_3,_2,X,...) X
#define DISCARD_ARGUMENTS(...)
#define OPTIONAL(APPLY_,...) \
THIRD(GLUE(OPTIONAL_SHIFT_IF_1_IS_,COUNT(__VA_ARGS__)),\
OPTIONAL_SINGLE_CASE,\
APPLY_OPTION) \
(APPLY_,__VA_ARGS__)
#define OPTIONAL_SHIFT_IF_1_IS_1 ,
#define OPTIONAL_SINGLE_CASE(APPLY_,...) \
THIRD(OPTIONAL_SHIFT_TEST __VA_ARGS__ (0_UNLOCK), \
DISCARD_ARGUMENTS, \
APPLY_OPTION)(APPLY_,)
#define OPTIONAL_SHIFT_TEST(...) GLUE(OPTIONAL_APPLY_SHIFT_TEST_,FIRST(__VA_ARGS__))
#define OPTIONAL_APPLY_SHIFT_TEST_0_UNLOCK ,
#define APPLY_OPTION(A,...) APPLY_OPTION_C(APPLY_OPTION_I,A)
#define APPLY_OPTION_C(A,B) A B
#define APPLY_OPTION_I(...) __VA_ARGS__
核心机制是“间接第三宏”;这里的想法是生成第一个参数,该参数应用一些“测试”,如果出现感兴趣的内容,则生成一个逗号,将第二个参数移动到选择之前的第三个位置。
OPTIONAL 使用了两次;如果有一个论点,则进行下一阶段测试,以查看该论点是否没有标记。此测试在OPTIONAL_SHIFT_TEST 和(0_UNLOCK) 之间注入参数的标记;如果没有标记,则进行调用,并且此宏将生成一个对象宏,该宏将创建移动逗号。这种间接方式是有意的,允许括号出现在第一个参数中而不会检测到错误(参见演示)。
需要什么才能使这个构造也能与 MSVC 完全兼容?
...内置在所有宏的间接层中的是“调用者宏”;这里,名字里都有_C,取两个参数A和B,简单展开为A B;它们的用途始终是将宏名称与宏参数集分开。 那些地址 MSVC。如果我真的试图定位 MSVC(无论出于何种原因),只需要一个这样的调用者;然而,通过为每个宏集创建一个调用者,我们也可以为 MSVC 和 gcc/clang 进行这项工作。 (ETA:THIRD 需要两个调用方间接调用;一次用于第三个本身的不同参数,另一个用于正确解释扩展的第一个参数的逗号,因为这是 THIRD 宏的全部意义)。
请注意,这不依赖于任何编译器特定的逗号省略技巧。
最后...有了OPTIONAL,您需要做的就是:
#define INNER(...) typedef Aggregator<__VA_ARGS__> AGG;
#define ENVIRONMENT(...) INNER(B, C OPTIONAL((,),__VA_ARGS__) __VA_ARGS__)
Godbolt 演示