如何一致地定义同时用作枚举、整数和字符串的选项列表?

How to consistently define list of options to use as enum, int and string at the same time?

我提供了一个显示分辨率的示例,但问题更笼统。

假设我们有以下配置:640x480; 1024x768; 1600x900。如果它们是单独定义的,它将看起来像:

std::string RESTR[] = { "640x480", "1024x768", "1600x900" };
struct Enum { enum ERes { _640x480, _1024x768, _1600x900 }; };
uint RES[][2] = { { 640, 480 }, { 1024, 768 }, { 1600, 900 } };

维护所有列表太容易出错。应该按以下方式给出(或至少是非常相似的方式):

Res(  640, 480 )
Res( 1024, 768 )
Res( 1600, 900 )

A Resolution class 似乎是一个很好的解决方案,它在需要时表现为一个字符串,returns 在其他用例中表现为一个值。但是它没有 运行 应用程序之前的枚举。为了在开发中使用解析枚举,需要编译时魔法、预处理器或模板定义。怎么解决的?

是否有针对此类问题的标准最先进方法?

如果您不习惯使用 enum,您可以使用类似以下内容的方法:

#define CONCAT2(A, B) A ## B
#define CONCAT(A, B) CONCAT2(A, B)

#define STR2(A) #A
#define STR(A) STR2(A)

#define DEFINE_RESOLUTION(ResClass, X, Y) \
   struct ResClass { \
      static const int val = CONCAT(0x, CONCAT(X, Y)); \
      static char const* toString() \
      { \
         return STR(CONCAT(X, CONCAT(x, Y))); \
      } \
      static int getX() { return (X); } \
      static int getY() { return (Y); } \
   };
DEFINE_RESOLUTION(Res1, 640, 480);
DEFINE_RESOLUTION(Res2, 1024, 768);

// Test the code
#include <iostream>    

int getint()
{
   return Res1::val;
   // return Res2::val;
}

int main()
{
   int v = getint();
   switch (v)
   {
      case Res1::val:
         std::cout << Res1::getX() << "\n";
         std::cout << Res1::getY() << "\n";
         std::cout << Res1::toString() << "\n";
         break;

      case Res2::val:
         std::cout << Res2::getX() << "\n";
         std::cout << Res2::getY() << "\n";
         std::cout << Res2::toString() << "\n";
         break;

      default:
         std::cout << "Got default\n";

   }
   return 0;
}

输出:

640
480
640x480

有两种不同的方法可以仅使用 C++ 工具来解决此类问题(它们都使用预处理器的宏):

  1. 基于 variadic macro (you can write your own code or use Boost.Preprocessor 库宏的方法。
  2. 基于X-macro technique的方法。

如果您使用 C++03 或更低版本,则第二种方法适合您(如果您使用 C++11 或更高版本,或者如果您的 C++ 编译器支持 C99,则第一种方法可用)。这是它的描述:

  • 创建一个包含文件没有包含守卫(你可以给它一个不同于你通常的头文件扩展名的扩展名,例如inc)名称 Resolution 和以下内容:

    RESOLUTION(  640, 480 )
    RESOLUTION( 1024, 768 )
    RESOLUTION( 1600, 900 )
    
  • 按如下方式定义数组:

    #define TO_STRING_LITERAL_(S) #S
    #define TO_STRING_LITERAL(S) TO_STRING_LITERAL_(S)
    #define STRING_RESOLUTION_(X, Y) X ## x ## Y
    #define STRING_RESOLUTION(X, Y) TO_STRING_LITERAL(STRING_RESOLUTION_(X, Y)),
    #define ENUM_RESOLUTION(X, Y) _ ## X ## x ## Y,
    #define INT_RESOLUTION(X, Y) { X , Y },
    
    #define RESOLUTION(X, Y) STRING_RESOLUTION(X, Y)
    std::string RESTR[] =
    {
    #    include "Resolution.inc"
    };
    #undef RESOLUTION
    
    #define RESOLUTION(X, Y) ENUM_RESOLUTION(X, Y)
    struct Enum
    {
         enum ERes
         {
    #        include "Resolution.inc"
         };
    };
    #undef
    
    #define RESOLUTION(X, Y) INT_RESOLUTION(X, Y)
    uint RES[][2] =
    {
    #   include "Resolution.inc"
    };
    #undef
    

更新: @rici 在评论中建议对 STRING_RESOLUTION 宏进行有趣的简化:

#define STRING_RESOLUTION(X, Y) #X "x" #Y

这是可能的,因为一系列字符串文字被 C++ 编译器识别为一个文字:"string1" "string2" "string3" == "string1string2string3"