【问题标题】:difference between named macro arguments and __VA_ARGS__命名宏参数和 __VA_ARGS__ 之间的区别
【发布时间】:2021-09-16 19:00:04
【问题描述】:

我编写了一些实用宏来将enum 值转换为std::string

#include <map>
#include <string>

#define MY_map_entry_(name, value) {#value, name::value}
#define MY_map_2(name, v1, v2) MY_map_entry_(name, v1), MY_map_entry_(name, v2)
#define MY_map_3(name, v1, v2, v3) MY_map_entry_(name, v1), MY_map_2(name, v2, v3)

这完全按预期工作:

enum class rgb { red, green, blue };
static const std::map<std::string, rgb> string_to_rgb{ MY_map_3(rgb, red, green, blue) };
// { {"red", rgb::red}, {"green", rgb::green}, {"blue", rgb::blue} }

但是,编写宏开始变得有些乏味;所以我想我会尝试__VA_ARGS__

#define MY_map_4(name, v1, ...) MY_map_entry_(name, v1), MY_map_3(name, __VA_ARGS__)

有一个用例

enum class cymk { cyan, yellow, magenta, black };
static const std::map<std::string, cymk> string_to_cymk{ MY_map_4(cymk, cyan, yellow, magenta, black) };

这不起作用,没有__VA_ARGS__ 的类似宏可以

#define MY_map_4(name, v1, v2, v3, v4) MY_map_entry_(name, v1), MY_map_3(name, v2, v3, v4)

有没有办法用__VA_ARGS__MY_map_4

(当然,如果不使用 any 宏,这样做会很好——或者至少可以接近——但我认为这是不可能的。)

【问题讨论】:

    标签: c++ c-preprocessor


    【解决方案1】:

    您真正想要的是 X 宏而不是可变参数:

    #define ENUM_VAL(X) X
    #define ENUM_TO_STRING(E, X) {E::X, #X}
    #define STRING_TO_ENUM(E, X) {#X, E::X}
    
    enum class rgb
    {
    #define RGB_VALS E(red), E(green), E(blue)
    #define E(X) ENUM_VAL(X)
        RGB_VALS
    #undef E
    };
    
    static const std::map<rgb, std::string> rgb_to_string
    ({
    #define E(X) ENUM_TO_STRING(rgb, X)
        RGB_VALS
    #undef E
    });
    
    static const std::map<std::string, rgb> string_to_rgb
    ({
    #define E(X) STRING_TO_ENUM(rgb, X)
        RGB_VALS
    #undef E
    });
    
    #undef RGB_VALS
    

    背后的想法是:您在使用辅助宏来实际生成代码的从属宏中定义枚举值。

    使用辅助宏,您可以交换从属宏的行为,并为不同的上下文获得所需的结果。

    【讨论】:

      【解决方案2】:

      您的宏没有问题。 GCC 和 Clang 开箱即用地接受它,而 MSVC 需要 /Zc:preprocessor(它启用符合标准的预处理器;默认预处理器是有缺陷的)。


      可以改进宏以支持无限枚举大小(不需要显式指定),并声明枚举和数组。这是一个例子。 (需要 C++20,Clang 还需要 -Wno-gnu-zero-variadic-macro-arguments。MSVC 需要 /Zc:preprocessor。)

      #define ENUM(name, seq) \
          enum class name { END(ENUM_DECL_A seq) }; \
          std::pair<std::string, name> CAT(name, _values)[] = { END(ENUM_ARR_A seq) };
      
      #define END(...) END_(__VA_ARGS__)
      #define END_(...) __VA_ARGS__##_END
      
      #define CAT(x, y) CAT_(x, y)
      #define CAT_(x, y) x##y
      
      #define STR(...) STR_(__VA_ARGS__)
      #define STR_(...) #__VA_ARGS__
      
      #define ENUM_DECL_A(...) ENUM_DECL_BODY(__VA_ARGS__) ENUM_DECL_B
      #define ENUM_DECL_B(...) ENUM_DECL_BODY(__VA_ARGS__) ENUM_DECL_A
      #define ENUM_DECL_A_END
      #define ENUM_DECL_B_END
      #define ENUM_DECL_BODY(name, ...) name __VA_OPT__(= __VA_ARGS__),
      
      #define ENUM_ARR_A(...) ENUM_ARR_BODY(__VA_ARGS__) ENUM_ARR_B
      #define ENUM_ARR_B(...) ENUM_ARR_BODY(__VA_ARGS__) ENUM_ARR_A
      #define ENUM_ARR_A_END
      #define ENUM_ARR_B_END
      #define ENUM_ARR_BODY(name, ...) STR(name),
      

      然后,ENUM(A, (x)(y,42)(z)) 扩展为:

      enum class A
      {
          x,
          y = 42,
          z,
      };
      
      std::pair<std::string, A> A_values[] = { "x", "y", "z", };
      

      【讨论】:

      • @JayBach 另一种方法是为不同的编译器提供不同的宏实现,这也很糟糕。
      猜你喜欢
      • 2019-09-03
      • 2020-08-21
      • 1970-01-01
      • 2013-03-29
      • 2020-03-05
      • 2020-10-21
      • 2013-01-04
      • 2016-05-28
      • 1970-01-01
      相关资源
      最近更新 更多