c++ - 具有 0..n 参数的跨平台可变参数宏

标签 c++ visual-c++ c-preprocessor template-meta-programming

对于参数包,我需要一个宏,它可以接受任意数量的参数(实际上是类型),它可以跨平台工作。此代码与 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,需要什么?

更新:

事实证明,空参数情况对 GCC 也不起作用:https://godbolt.org/z/1zKZO- .

最佳答案

这是一种实际上可以满足您要求的方法...我已将其预定义为与 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 之间注入(inject)参数的标记和 (0_UNLOCK) ;如果没有 token ,则进行调用,并且此宏将生成一个对象宏来创建移动逗号。这种间接寻址是有意的,允许括号位于第一个参数中而不会出现错误检测(参见演示)。

What is required to make this construct also work fully with MSVC?

...内置于所有宏的间接层中的是“调用者宏”;在这里,他们都有_C在名称中,带两个参数 AB , 并简单地扩展到 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__)

神马演示

关于c++ - 具有 0..n 参数的跨平台可变参数宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55666397/

相关文章:

c++ - 使用 UDP 的电子邮件客户端/服务器

c++ - 我看到的所有基于 std::move() 的 std::swap() 实现都是错误的吗?

winapi - 如何从 DLL 获取 HINSTANCE?

c - C 对目标文件有任何保证吗?

c++ - Sun Studio 和 "",第 1 行 : Illegal flag (-)

c++ - 如何将 Unicode 字符串转换为 utf-8 或 utf-16 字符串?

c++ - Itanium 和 MSVC ABI 中跨模块边界的 RTTI

c++ - 在 "no default constructor"的构造函数的第一个括号中获取 C2512 `ClassA` for `ClassB` 错误?

c++ - 获取 if 语句以检查定义的宏

c - 有没有办法检查宏是否被定义为特定的东西?