对于参数包,我需要一个宏,它可以采用任意数量的参数(实际类型),它可以跨平台工作。此代码与GCC,LLVM和MSVC很好地协作(在预处理器重新编写为support the ##
sequence之后(参见行为4 [可变参数宏中的逗号]):
class A {};
class B: A {};
class C: A {};
class D: A {};
template<typename... Interfaces>
class Aggregator: public Interfaces... {
};
#define INNER(...) typedef Aggregator<__VA_ARGS__> AGG;
#define ENVIRONMENT(...) INNER(B, C, ## __VA_ARGS__)
ENVIRONMENT(D)
这里的问题是空参数情况(ENVIRONMENT()
)。由于我还不能使用C ++ 20(它带有__VA_OPT__()
令牌序列,我必须找到一个最多需要C ++ 17的解决方案.GCC + LLVM对空参数列表没有问题,但是MSVC坚持在至少有一个参数可供逗号使用。
使这个结构与MSVC完全兼容需要什么?
这是一种实际完成你所要求的方法......我已经预定了这个与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)
之间注入参数的标记;如果没有令牌,则进行调用,此宏将生成一个创建移位逗号的对象宏。这种间接是有意的,允许括号在第一个参数中而没有错误检测(参见演示)。
What is required to make this construct also work fully with 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演示
这样的事情怎么样?
template <class... X>
using INNER = Aggregator<B, C, X...>;
#define ENVIRONMENT(...) typedef INNER<__VA_ARGS__> AGG
ENVIRONMENT(); // Or ENVIRONMENT(D)
希望这可以帮助...