【问题标题】:Cross platform variadic macro with 0..n parameters具有 0..n 参数的跨平台可变参数宏
【发布时间】:2019-04-13 14:09:46
【问题描述】:

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

【问题讨论】:

  • "GCC + LLVM 对空参数列表没有问题" - they actually do have a problem,除非使用 gnu++ 模式编译。这个特定的例子可能会使用可变别名模板。
  • 有趣,似乎我需要专门针对 GCC 进行更多测试,但是我已经看到了很多没有提及 GNU 扩展要求的令牌粘贴序列解决方案。使用(Apple)LLVM 10(不使用 GNU 扩展),它可以在没有任何警告的情况下编译。

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


【解决方案1】:

这是一种实际上可以满足您要求的方法...我已经将其预定义为与 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,取两个参数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__)

Godbolt 演示

【讨论】:

    【解决方案2】:

    这样的事情怎么样?

    template <class... X>
    using INNER = Aggregator<B, C, X...>;
    
    #define ENVIRONMENT(...) typedef INNER<__VA_ARGS__> AGG
    
    ENVIRONMENT(); // Or ENVIRONMENT(D)
    

    希望这会有所帮助...

    【讨论】:

      猜你喜欢
      • 2011-04-04
      • 2013-05-21
      • 2011-04-03
      • 1970-01-01
      • 1970-01-01
      • 2011-01-02
      • 1970-01-01
      • 2011-07-14
      相关资源
      最近更新 更多