如何一致地定义同时用作枚举、整数和字符串的选项列表?
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++ 工具来解决此类问题(它们都使用预处理器的宏):
- 基于 variadic macro (you can write your own code or use Boost.Preprocessor 库宏的方法。
- 基于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"
。
我提供了一个显示分辨率的示例,但问题更笼统。
假设我们有以下配置: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++ 工具来解决此类问题(它们都使用预处理器的宏):
- 基于 variadic macro (you can write your own code or use Boost.Preprocessor 库宏的方法。
- 基于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"
。