C++获取枚举元素数量#
最近有个特殊需求,需要获取一个枚举类型的元素数量,于是学到了以下骚操作。
操作源自网站stack overflow的一个提问《是否可以确定 c++ 枚举类的元素数量?》。
手艺人#
首先是最简单的:
1
|
enum class Example { A, B, C, D, E, Count };
|
由于枚举值默认从0递增,通过static_cast<int>(Example::Count)
就可以获取数量。
上述方法不适用于有自定义值的枚举,例如:
1
|
enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 };
|
需要手动计数无疑很不便且枯燥。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
enum class Example { A, B, C, D, E };
constexpr int ExampleCount = [] {
Example e{};
int count = 0;
switch (e) {
case Example::A:
count++;
case Example::B:
count++;
case Example::C:
count++;
case Example::D:
count++;
case Example::E:
count++;
}
return count;
}();
|
仅表示对此实现的看法:更多的代码意味着更多的错误。
当你对这个枚举类使用switch
时,编译器会警告你缺少一个case
,不优雅。
另外上述方式适用面窄且让人成为了程序的最大风险,老子曰过:“凡可自动化,皆使自动化”。
宏魔法#
__LINE__
宏定义#
众所周知__LINE__
表示当前行号,那么编写如下代码:
1
2
3
4
5
6
7
8
9
|
// clang-format off
constexpr auto TEST_START_LINE = __LINE__;
enum class TEST { // Subtract extra lines from TEST_SIZE if an entry takes more than one
ONE = 7
, TWO = 6
, THREE = 9
};
constexpr auto TEST_SIZE = __LINE__ - TEST_START_LINE - 3;
// clang-format on
|
通过行号相减,并关闭clang-format确保代码格式化不会破坏格式,即可得出元素数量。
GCC非标宏__COUNTER__
#
__COUNTER__
是 GNU 编译器的非标准编译器扩展。
可以认为它是一个计数器,代表一个整数,它的值一般被初始化为0,在每次编译器编译到它时,会自动 +1 。
那么可以实现如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
constexpr int COUNTER(int val, int )
{
return val;
}
constexpr int E_START{__COUNTER__};
enum class E
{
ONE = COUNTER(90, __COUNTER__) , TWO = COUNTER(1990, __COUNTER__)
};
template<typename T>
constexpr T E_SIZE = __COUNTER__ - E_START - 1;
|
Boost#
果使用 boost 的预处理器实用程序,则可以使用 BOOST_PP_SEQ_SIZE(...)
获取计数。
例如,可以按如下方式定义 CREATE_ENUM
宏:
1
2
3
4
5
6
7
8
9
10
11
12
|
#include <boost/preprocessor.hpp>
#define ENUM_PRIMITIVE_TYPE std::int32_t
#define CREATE_ENUM(EnumType, enumValSeq) \
enum class EnumType : ENUM_PRIMITIVE_TYPE \
{ \
BOOST_PP_SEQ_ENUM(enumValSeq) \
}; \
static constexpr ENUM_PRIMITIVE_TYPE EnumType##Count = \
BOOST_PP_SEQ_SIZE(enumValSeq); \
// END MACRO
|
然后,调用宏:
1
|
CREATE_ENUM(Example, (A)(B)(C)(D)(E));
|
宏展开将生成以下代码:
1
2
3
4
5
|
enum class Example : std::int32_t
{
A, B, C, D, E
};
static constexpr std::int32_t ExampleCount = 5;
|
这只是 boost 预处理器工具的冰山一角。例如,宏还可以为强类型枚举定义 to/from 字符串转换实用程序和 ostream 运算符。
阅读有关 boost 预处理器工具的更多信息。
利用可变参数__VA_ARGS__
#
1
2
3
4
5
6
7
8
|
#define Enum(Name, ...) \
struct Name { \
enum : int { \
__VA_ARGS__ \
}; \
private: struct en_size { int __VA_ARGS__; }; \
public: static constexpr size_t count = sizeof(en_size)/sizeof(int); \
}
|
用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
struct S {
Enum(TestEnum, a=11, b=22, c=33);
void Print() {
std::cout << TestEnum::a << '\n';
std::cout << TestEnum::b << '\n';
std::cout << TestEnum::count << '\n';
}
};
int main()
{
S d;
d.Print();
return 0
}
|
输出:
全场最佳——依旧是可变参数__VA_ARGS__
#
这可以通过 std::initializer_list
的技巧来解决:
1
2
3
4
5
6
7
8
9
|
#define TypedEnum(Name, Type, ...) \
struct Name { \
enum : Type{ \
__VA_ARGS__ \
}; \
static inline const size_t count = []{ \
static Type __VA_ARGS__; return std::size({__VA_ARGS__}); \
}(); \
};
|
用法:
1
2
3
4
5
6
7
8
|
#define Enum(Name, ...) TypedEnum(Name, int, _VA_ARGS_)
Enum(FakeEnum, A = 1, B = 0, C)
int main()
{
std::cout << FakeEnum::A << std::endl
<< FakeEnun::count << std::endl;
}
|
反射未来可期#
C++ Reflection Technical Specification(反射技术规范,以下简称反射TS),特别是最新版本的反射 TS 草案的 [reflect.ops.enum]/2,提供了 get_enumerators
、TransformationTrait
操作:
[reflect.ops.enum]/2
1
|
template <Enum T> struct get_enumerators
|
get_enumerators<T>
的所有特化都应满足 TransformationTrait
要求 (20.10.1)。名为 type
的嵌套类型指定满足 ObjectSequence
的元对象类型,其中包含满足 Enumerator
并反射 T
所反映的枚举类型的枚举数的元素。
草案的 [reflect.ops.objseq] 涵盖了 ObjectSequence
操作,其中 [reflect.ops.objseq]/1 特别涵盖了提取满足 ObjectSequence
的元对象的元素数的 get_size
特性:
[reflect.ops.objseq]/1
1
|
template <ObjectSequence T> struct get_size;
|
get_size<T>
的所有特化都应满足 UnaryTypeTrait
要求 (20.10.1),其基本特征为 integral_constant<size_t,N>
,其中 N
是对象序列中的元素数。
因此,在 Reflection TS 中,要以当前形式接受和实现,枚举的元素数可以在编译时计算,如下所示:
1
2
3
4
5
|
enum class Example { A, B, C, D, E };
using ExampleEnumerators = get_enumerators<Example>::type;
static_assert(get_size<ExampleEnumerators>::value == 5U, "");
|
我们可能会看到别名模板 get_enumerators_v
和 get_type_v
以进一步简化反射:
1
2
3
4
5
|
enum class Example { A, B, C, D, E };
using ExampleEnumerators = get_enumerators_t<Example>;
static_assert(get_size_v<ExampleEnumerators> == 5U, "");
|
正如 Herb Sutter 的旅行报告所述:夏季 ISO C++ 标准会议 (Rapperswil) 从 2018 年 6 月 9 日的 ISO C++ 委员会夏季会议开始,Reflection TS 已被宣布为功能完成
Reflection TS 功能齐全:Reflection TS 已宣布功能完整,并将在夏季进行主要意见投票。再次注意,TS 当前基于模板元编程的语法只是一个占位符;所请求的反馈是关于设计的核心 “guts” 的,委员会已经知道它打算用一种更简单的编程模型来取代表面语法,该模型使用普通的编译时代码,而不是 <>
风格的元编程。
最初计划用于 C++20,但目前尚不清楚 Reflection TS 是否仍有机会进入 C++20 版本。