C++:简化#define
C++: Simplifiying a #define
我有一个 #define 生成一个枚举 class 和一个对应的输出运算符生成的枚举 class。(见下文)
#define ENUM(N, T, N1, V1, N2, V2, N3, V3, N4, V4, N5, V5, N6, V6, N7, V7)\
enum class N : T {\
N1 = V1,\
N2 = V2,\
N3 = V3,\
N4 = V4,\
N5 = V5,\
N6 = V6,\
N7 = V7,\
};\
std::ostream &operator <<(std::ostream &os, const N val); /* declare function to avoid compiler warning */\
std::ostream &operator <<(std::ostream &os, const N val) {\
switch (val) {\
case N::N1:\
os << #N1;\
break;\
case N::N2:\
os << #N2;\
break;\
case N::N3:\
os << #N3;\
break;\
case N::N4:\
os << #N4;\
break;\
case N::N5:\
os << #N5;\
break;\
case N::N6:\
os << #N6;\
break;\
case N::N7:\
os << #N7;\
break;\
}\
if (sizeof(T) == 1) {\
os << '(' << static_cast<int>(val) << ')';\
} else {\
os << '(' << static_cast<T>(val) << ')';\
}\
return os;\
}
在这个例子中可以这样使用:
#include <cstdlib>
#include <iostream>
#include <ostream>
ENUM(Weekdays, unsigned char, Monday, 10, Tuesday, 12, Wednesday, 14, Thursday, 16, Friday, 18, Saterday, 100, Sunday, 101)
int main(const int /*argc*/, const char *const /*argv*/[]) {
Weekdays test = Weekdays::Monday;
std::cout << test << std::endl;
std::cout << Weekdays::Tuesday << std::endl;
std::cout << Weekdays::Sunday << std::endl;
return EXIT_SUCCESS;
}
此处生成的输出:
Monday(10)
Tuesday(12)
Sunday(101)
我的解决方案有一些限制:
- 每个枚举都需要一个初始化值
- 固定为7个枚举值
对于更广泛的用法,我有两个问题。尤其是第二个会大大提高可用性。
这里有我的问题:
- 如何避免为每个枚举值定义一个初始化值?
(就像在真实的枚举中一样)
- 有什么想法可以概括 #define 以使用任意数量的值吗?
我正在等待您对我的代码提出意见和改进建议。
莱纳
相对接近你现在拥有的东西,你可以利用 Boost.Preprocessor 中的 BOOST_PP_SEQ_FOR_EACH 宏,它看起来像这样:
#include <boost/preprocessor.hpp>
#define ENUM_FIELD(I,_,F) F,
#define ENUM_OUTPUT_CASE(I,N,F) case N::F: os << BOOST_PP_STRINGIZE(F); break;
#define ENUM(N, T, ARGS) \
enum class N : T {\
BOOST_PP_SEQ_FOR_EACH(ENUM_FIELD,N,ARGS)\
};\
std::ostream &operator <<(std::ostream &os, const N val) {\
switch (val) {\
BOOST_PP_SEQ_FOR_EACH(ENUM_OUTPUT_CASE,N,ARGS)\
}\
\
os << '(' << static_cast<int>(val) << ')';\
return os;\
}
ENUM(Weekdays, unsigned char, (Monday)(Tuesday)(Wednesday)(Thursday)(Friday)(Saturday)(Sunday))
这消除了提供值的重复和可能性。整个过程更短了,可以说是以降低可读性并可能更难调试为代价的——我不会权衡使用这些宏的利弊。
请注意,我已经更改了将参数传递给 ENUM 宏的方式:现在这是一个 Boost.Preprocessor 序列 。您最多应该能够传递 256 个元素;有关更多信息和更多适用于序列的宏,请参阅 documentation。
如果你不需要知道这个编译时间,你可以使用像 Protobuf 这样的库来定义你的枚举。C++ 中的 Protobuf 支持枚举描述符,它可以用作反射的一种形式。这两篇文章描述了使用 Protobuf 的可能解决方案 ( and )。
编辑:
我忘了还有另一个库可能对你有用,如果你需要它编译时间。它称为 Frozen 并提供编译时映射。您也许能够生成一些定义映射的代码,并使用它将枚举值转换为字符串。
我让它为我工作。添加了一些特殊功能:
- 操纵器切换on/off输出枚举的值
(在枚举后面的括号中)
- 输出非法值
(不应该发生:查看可能发生的代码)
这是我的完整解决方案:
#include <cstdlib>
#include <iostream>
#include <ostream>
#include <boost/preprocessor.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
class EnumShowValue {
private:
static bool showValueFlag;
public:
explicit EnumShowValue(const bool flag) { EnumShowValue::showValueFlag = flag; }
static bool showValue() { return EnumShowValue::showValueFlag; }
};
bool EnumShowValue::showValueFlag = false;
inline std::ostream &operator <<(std::ostream &os, const EnumShowValue &) { return os; }
#define ENUM_FIELD(I,_,F)\
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(F),2),\
BOOST_PP_TUPLE_ELEM(0,F)=BOOST_PP_TUPLE_ELEM(1,F),\
BOOST_PP_TUPLE_ELEM(0,F)),
#define ENUM_OUTPUT_CASE(I,N,F)\
case N::BOOST_PP_TUPLE_ELEM(0,F):\
os << BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(0,F));\
break;
#define ENUM(N, T, ARGS) \
enum class N : T {\
BOOST_PP_SEQ_FOR_EACH(ENUM_FIELD,N,ARGS)\
};\
std::ostream &operator <<(std::ostream &os, const N val);\
std::ostream &operator <<(std::ostream &os, const N val) {\
switch (val) {\
BOOST_PP_SEQ_FOR_EACH(ENUM_OUTPUT_CASE,N,ARGS)\
default:\
os << "illegal value: " << BOOST_PP_STRINGIZE(N);\
if (!EnumShowValue::showValue()) {\
os << '(';\
if (sizeof(T) == 1) {\
os << static_cast<int>(val);\
} else {\
os << static_cast<T>(val);\
}\
os << ')';\
}\
}\
if (EnumShowValue::showValue()) {\
os << '(';\
if (sizeof(T) == 1) {\
os << static_cast<int>(val);\
} else {\
os << static_cast<T>(val);\
}\
os << ')';\
}\
return os;\
}
ENUM(Weekdays, unsigned char, ((Monday, 101))((Tuesday))((Wednesday))((Thursday))((Friday))((Saturday, 200))((Sunday)))
int main(const int /*argc*/, const char *const /*argv*/[]) {
std::cout << Weekdays::Monday << std::endl;
std::cout << Weekdays::Tuesday << std::endl;
std::cout << Weekdays::Wednesday << std::endl;
std::cout << Weekdays::Thursday << std::endl;
std::cout << Weekdays::Friday << std::endl;
std::cout << Weekdays::Saturday << std::endl;
std::cout << Weekdays::Sunday << std::endl;
std::cout << Weekdays(99) << std::endl;
std::cout << EnumShowValue(true);
std::cout << Weekdays::Monday << std::endl;
std::cout << Weekdays::Tuesday << std::endl;
std::cout << Weekdays::Wednesday << std::endl;
std::cout << Weekdays::Thursday << std::endl;
std::cout << Weekdays::Friday << std::endl;
std::cout << Weekdays::Saturday << std::endl;
std::cout << EnumShowValue(false);
std::cout << Weekdays::Sunday << std::endl;
std::cout << Weekdays(-1) << std::endl;
return EXIT_SUCCESS;
}
和相应的输出:
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
illegal value: Weekdays(99)
Monday(101)
Tuesday(102)
Wednesday(103)
Thursday(104)
Friday(105)
Saturday(200)
Sunday
illegal value: Weekdays(255)
我有一个 #define 生成一个枚举 class 和一个对应的输出运算符生成的枚举 class。(见下文)
#define ENUM(N, T, N1, V1, N2, V2, N3, V3, N4, V4, N5, V5, N6, V6, N7, V7)\
enum class N : T {\
N1 = V1,\
N2 = V2,\
N3 = V3,\
N4 = V4,\
N5 = V5,\
N6 = V6,\
N7 = V7,\
};\
std::ostream &operator <<(std::ostream &os, const N val); /* declare function to avoid compiler warning */\
std::ostream &operator <<(std::ostream &os, const N val) {\
switch (val) {\
case N::N1:\
os << #N1;\
break;\
case N::N2:\
os << #N2;\
break;\
case N::N3:\
os << #N3;\
break;\
case N::N4:\
os << #N4;\
break;\
case N::N5:\
os << #N5;\
break;\
case N::N6:\
os << #N6;\
break;\
case N::N7:\
os << #N7;\
break;\
}\
if (sizeof(T) == 1) {\
os << '(' << static_cast<int>(val) << ')';\
} else {\
os << '(' << static_cast<T>(val) << ')';\
}\
return os;\
}
在这个例子中可以这样使用:
#include <cstdlib>
#include <iostream>
#include <ostream>
ENUM(Weekdays, unsigned char, Monday, 10, Tuesday, 12, Wednesday, 14, Thursday, 16, Friday, 18, Saterday, 100, Sunday, 101)
int main(const int /*argc*/, const char *const /*argv*/[]) {
Weekdays test = Weekdays::Monday;
std::cout << test << std::endl;
std::cout << Weekdays::Tuesday << std::endl;
std::cout << Weekdays::Sunday << std::endl;
return EXIT_SUCCESS;
}
此处生成的输出:
Monday(10)
Tuesday(12)
Sunday(101)
我的解决方案有一些限制:
- 每个枚举都需要一个初始化值
- 固定为7个枚举值
对于更广泛的用法,我有两个问题。尤其是第二个会大大提高可用性。
这里有我的问题:
- 如何避免为每个枚举值定义一个初始化值?
(就像在真实的枚举中一样) - 有什么想法可以概括 #define 以使用任意数量的值吗?
我正在等待您对我的代码提出意见和改进建议。
莱纳
相对接近你现在拥有的东西,你可以利用 Boost.Preprocessor 中的 BOOST_PP_SEQ_FOR_EACH 宏,它看起来像这样:
#include <boost/preprocessor.hpp>
#define ENUM_FIELD(I,_,F) F,
#define ENUM_OUTPUT_CASE(I,N,F) case N::F: os << BOOST_PP_STRINGIZE(F); break;
#define ENUM(N, T, ARGS) \
enum class N : T {\
BOOST_PP_SEQ_FOR_EACH(ENUM_FIELD,N,ARGS)\
};\
std::ostream &operator <<(std::ostream &os, const N val) {\
switch (val) {\
BOOST_PP_SEQ_FOR_EACH(ENUM_OUTPUT_CASE,N,ARGS)\
}\
\
os << '(' << static_cast<int>(val) << ')';\
return os;\
}
ENUM(Weekdays, unsigned char, (Monday)(Tuesday)(Wednesday)(Thursday)(Friday)(Saturday)(Sunday))
这消除了提供值的重复和可能性。整个过程更短了,可以说是以降低可读性并可能更难调试为代价的——我不会权衡使用这些宏的利弊。
请注意,我已经更改了将参数传递给 ENUM 宏的方式:现在这是一个 Boost.Preprocessor 序列 。您最多应该能够传递 256 个元素;有关更多信息和更多适用于序列的宏,请参阅 documentation。
如果你不需要知道这个编译时间,你可以使用像 Protobuf 这样的库来定义你的枚举。C++ 中的 Protobuf 支持枚举描述符,它可以用作反射的一种形式。这两篇文章描述了使用 Protobuf 的可能解决方案 (
编辑: 我忘了还有另一个库可能对你有用,如果你需要它编译时间。它称为 Frozen 并提供编译时映射。您也许能够生成一些定义映射的代码,并使用它将枚举值转换为字符串。
我让它为我工作。添加了一些特殊功能:
- 操纵器切换on/off输出枚举的值
(在枚举后面的括号中) - 输出非法值
(不应该发生:查看可能发生的代码)
这是我的完整解决方案:
#include <cstdlib>
#include <iostream>
#include <ostream>
#include <boost/preprocessor.hpp>
#include <boost/preprocessor/tuple/elem.hpp>
class EnumShowValue {
private:
static bool showValueFlag;
public:
explicit EnumShowValue(const bool flag) { EnumShowValue::showValueFlag = flag; }
static bool showValue() { return EnumShowValue::showValueFlag; }
};
bool EnumShowValue::showValueFlag = false;
inline std::ostream &operator <<(std::ostream &os, const EnumShowValue &) { return os; }
#define ENUM_FIELD(I,_,F)\
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(F),2),\
BOOST_PP_TUPLE_ELEM(0,F)=BOOST_PP_TUPLE_ELEM(1,F),\
BOOST_PP_TUPLE_ELEM(0,F)),
#define ENUM_OUTPUT_CASE(I,N,F)\
case N::BOOST_PP_TUPLE_ELEM(0,F):\
os << BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(0,F));\
break;
#define ENUM(N, T, ARGS) \
enum class N : T {\
BOOST_PP_SEQ_FOR_EACH(ENUM_FIELD,N,ARGS)\
};\
std::ostream &operator <<(std::ostream &os, const N val);\
std::ostream &operator <<(std::ostream &os, const N val) {\
switch (val) {\
BOOST_PP_SEQ_FOR_EACH(ENUM_OUTPUT_CASE,N,ARGS)\
default:\
os << "illegal value: " << BOOST_PP_STRINGIZE(N);\
if (!EnumShowValue::showValue()) {\
os << '(';\
if (sizeof(T) == 1) {\
os << static_cast<int>(val);\
} else {\
os << static_cast<T>(val);\
}\
os << ')';\
}\
}\
if (EnumShowValue::showValue()) {\
os << '(';\
if (sizeof(T) == 1) {\
os << static_cast<int>(val);\
} else {\
os << static_cast<T>(val);\
}\
os << ')';\
}\
return os;\
}
ENUM(Weekdays, unsigned char, ((Monday, 101))((Tuesday))((Wednesday))((Thursday))((Friday))((Saturday, 200))((Sunday)))
int main(const int /*argc*/, const char *const /*argv*/[]) {
std::cout << Weekdays::Monday << std::endl;
std::cout << Weekdays::Tuesday << std::endl;
std::cout << Weekdays::Wednesday << std::endl;
std::cout << Weekdays::Thursday << std::endl;
std::cout << Weekdays::Friday << std::endl;
std::cout << Weekdays::Saturday << std::endl;
std::cout << Weekdays::Sunday << std::endl;
std::cout << Weekdays(99) << std::endl;
std::cout << EnumShowValue(true);
std::cout << Weekdays::Monday << std::endl;
std::cout << Weekdays::Tuesday << std::endl;
std::cout << Weekdays::Wednesday << std::endl;
std::cout << Weekdays::Thursday << std::endl;
std::cout << Weekdays::Friday << std::endl;
std::cout << Weekdays::Saturday << std::endl;
std::cout << EnumShowValue(false);
std::cout << Weekdays::Sunday << std::endl;
std::cout << Weekdays(-1) << std::endl;
return EXIT_SUCCESS;
}
和相应的输出:
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
illegal value: Weekdays(99)
Monday(101)
Tuesday(102)
Wednesday(103)
Thursday(104)
Friday(105)
Saturday(200)
Sunday
illegal value: Weekdays(255)