是否可以确定 c++ 的基数enum class
:
enum class Example { A, B, C, D, E };
我尝试使用sizeof
,但是,它返回枚举元素的大小。
sizeof(Example); // Returns 4 (on my architecture)
有没有获得基数的标准方法(在我的示例中为 5)?
是否可以确定 c++ 的基数enum class
:
enum class Example { A, B, C, D, E };
我尝试使用sizeof
,但是,它返回枚举元素的大小。
sizeof(Example); // Returns 4 (on my architecture)
有没有获得基数的标准方法(在我的示例中为 5)?
不是直接的,但您可以使用以下技巧:
enum class Example { A, B, C, D, E, Count };
然后基数可用作static_cast<int>(Example::Count)
。
当然,这只有在让枚举的值从 0 开始自动分配的情况下才能很好地工作。如果不是这样,您可以手动将正确的基数分配给 Count,这实际上与必须维护一个单独的常量没有什么不同反正:
enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 };
一个缺点是编译器将允许您将其Example::Count
用作枚举值的参数——所以使用它时要小心!(不过,我个人认为这在实践中不是问题。)
对于 C++17,您可以magic_enum::enum_count
从 lib https://github.com/Neargye/magic_enum使用:
magic_enum::enum_count<Example>()
-> 4。
该库使用特定于编译器的 hack(基于__PRETTY_FUNCTION__
/ __FUNCSIG__
),它适用于 Clang >= 5、MSVC >= 15.3 和 GCC >= 9。
我们遍历给定的区间范围,并找到所有具有名称的枚举,这将是它们的计数。阅读有关限制的更多信息
在这篇文章https://taylorconor.com/blog/enum-reflection中有更多关于这个 hack 的信息。
// 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
这源自UglyCoder 的答案,但通过三种方式对其进行了改进。
BEGIN
and SIZE
) 中没有额外的元素(Cameron 的回答也有这个问题。)
它保留了UglyCoder相对于Cameron 的回答的优势,即可以为枚举数分配任意值。
一个问题(与UglyCoder共享但与Cameron不共享)是它使换行符和注释变得重要......这是出乎意料的。TEST_SIZE
因此,有人可以在不调整' 计算的情况下添加带有空格或注释的条目。这意味着代码格式化程序可以打破这一点。在evg656e发表评论后,我将答案编辑为 disable clang-format
,但如果您使用不同的格式化程序,请注意购买者。
// clang-format off
enum class TEST
{
BEGIN = __LINE__
, ONE
, TWO
, NUMBER = __LINE__ - BEGIN - 1
};
// clang-format on
auto const TEST_SIZE = TEST::NUMBER;
// or this might be better
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;
它可以通过 std::initializer_list 的技巧来解决:
#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__}); \
}(); \
};
用法:
#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;
}
有一个基于 X()-macros: image 的技巧,你有以下枚举:
enum MyEnum {BOX, RECT};
将其重新格式化为:
#define MyEnumDef \
X(BOX), \
X(RECT)
然后下面的代码定义了枚举类型:
enum MyEnum
{
#define X(val) val
MyEnumDef
#undef X
};
以下代码计算枚举元素的数量:
template <typename ... T> void null(T...) {}
template <typename ... T>
constexpr size_t countLength(T ... args)
{
null(args...); //kill warnings
return sizeof...(args);
}
constexpr size_t enumLength()
{
#define XValue(val) #val
return countLength(MyEnumDef);
#undef XValue
}
...
std::array<int, enumLength()> some_arr; //enumLength() is compile-time
std::cout << enumLength() << std::endl; //result is: 2
...
您可以尝试的一个技巧是在列表末尾添加一个枚举值并将其用作大小。在你的例子中
enum class Example { A, B, C, D, E, ExampleCount };
反射 TS ,特别是反射 TS 草案最新版本的[reflect.ops.enum]/2提供了以下get_enumerators
TransformationTrait
操作:
[reflect.ops.enum]/2
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 涵盖了get_size
提取满足以下条件的元对象的元素数量的特征ObjectSequence
:
[reflect.ops.objseq]/1
template <ObjectSequence T> struct get_size;
的所有特化
get_size<T>
应满足UnaryTypeTrait
具有基本特征的要求 (20.10.1)integral_constant<size_t, N>
,其中N
是对象序列中元素的数量。
因此,在反射 TS 被接受并以其当前形式实现时,可以在编译时计算枚举的元素数量,如下所示:
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
进一步简化反射:
enum class Example { A, B, C, D, E };
using ExampleEnumerators = get_enumerators_t<Example>;
static_assert(get_size_v<ExampleEnumerators> == 5U, "");
正如 Herb Sutter 的旅行报告: 2018 年 6 月 9 日 ISO C++ 委员会夏季会议的夏季 ISO C++ 标准会议 (Rapperswil) 所述,Reflection TS 已被宣布为功能完备
Reflection TS is feature-complete:Reflection TS 被宣布为功能完整,并将在夏季进行主要评论投票。再次注意,TS 当前基于模板元编程的语法只是一个占位符;所要求的反馈是关于设计的核心“胆量”,委员会已经知道它打算用更简单的编程模型替换表面语法,该模型使用普通的编译时代码而不是
<>
风格的元编程。
最初计划用于 C++20,但目前尚不清楚 Reflection TS 是否仍有机会进入 C++20 版本。
如果您使用 boost 的预处理器实用程序,您可以使用BOOST_PP_SEQ_SIZE(...)
.
例如,可以将CREATE_ENUM
宏定义如下:
#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
然后,调用宏:
CREATE_ENUM(Example, (A)(B)(C)(D)(E));
将生成以下代码:
enum class Example : std::int32_t
{
A, B, C, D, E
};
static constexpr std::int32_t ExampleCount = 5;
这只是涉及到 boost 预处理器工具的皮毛。例如,您的宏还可以为您的强类型枚举定义 to/from 字符串转换实用程序和 ostream 运算符。
更多关于提升预处理器工具的信息: https ://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html
顺便说一句,我碰巧强烈同意@FantasticMrFox 的观点,即Count
在接受的答案中使用的额外枚举值如果使用switch
语句会造成编译器警告问题。我发现unhandled case
编译器警告对于更安全的代码维护非常有用,所以我不想破坏它。
还有另一种不依赖行数或模板的方法。唯一的要求是将枚举值粘贴在它们自己的文件中,并使预处理器/编译器像这样进行计数:
my_enum_inc.h
ENUMVAL(BANANA)
ENUMVAL(ORANGE=10)
ENUMVAL(KIWI)
...
#undef ENUMVAL
我的枚举.h
typedef enum {
#define ENUMVAL(TYPE) TYPE,
#include "my_enum_inc.h"
} Fruits;
#define ENUMVAL(TYPE) +1
const size_t num_fruits =
#include "my_enum_inc.h"
;
这允许您使用枚举值添加注释,重新分配值,并且不会注入需要在代码中忽略/考虑的无效“计数”枚举值。
如果您不关心评论,则不需要额外的文件,并且可以像上面提到的那样做,例如:
#define MY_ENUM_LIST \
ENUMVAL(BANANA) \
ENUMVAL(ORANGE = 7) \
ENUMVAL(KIWI)
#include "my_enum_inc.h"
并用 MY_ENUM_LIST替换指令,但#undef ENUMVAL
每次使用后都需要。
另一种“愚蠢”的解决方案是:
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;
}();
通过与-Werror=switch
您一起编译,确保在您省略或重复任何 switch case 时收到编译器警告。它也是 constexpr 所以这是在编译时计算的。
但请注意,即使enum class
枚举的第一个值不是 0,即使默认初始化值为 0。因此,您必须从 0 开始或显式使用第一个值。
不,你必须把它写在代码中。
您还可以考虑static_cast<int>(Example::E) + 1
哪个消除了额外的元素。
这是在 2020 年对我有用的解决方案,使用 Visual Studio 2019
#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); \
}
用法:
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
}
输出:
11
22
3