是否可以确定C ++枚举类的元素数量?


84

是否有可能确定c ++的基数enum class

enum class Example { A, B, C, D, E };

我尝试使用sizeof,但是它返回枚举元素的大小。

sizeof(Example); // Returns 4 (on my architecture)

是否有获取基数的标准方法(在我的示例中为5)?


我认为可能存在特定的c ++ 11机制
bquenin

6
顺便说一下,这不是重复的。enumenum classes是非常不同的概念。
Shoe 2013年

@Shoe ...虽然真的吗?
凯尔·斯特兰德

1
这似乎是XY问题,我知道这是很久以前的事情了,但是您还记得为什么需要这样做吗?您不能迭代一个enum class值,那么知道这个数字会有什么好处呢?
神奇的狐狸先生

Answers:


70

不直接,但是您可以使用以下技巧:

enum class Example { A, B, C, D, E, Count };

然后,基数可作为static_cast<int>(Example::Count)

当然,只有在从0开始自动分配枚举值的情况下,这种方法才能很好地工作。无论如何:

enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 };

一个缺点是编译器将允许您将其Example::Count用作枚举值的参数-如果使用此值,请务必小心!(不过,我个人认为这实际上不是问题。)


1
枚举值在枚举类中是类型安全的,因此“ Count”将在此处为Example类型,而不是int,对吗?您必须先将'Count'强制转换为int才能使用它的大小。
一种方式的人

@Man:是的,此技巧与enum classes相比而不是plain enums有点混乱。为了清楚起见,我将进行演员表编辑。
卡梅伦

11
如果您在此枚举中使用switch语句,则任何不错的编译器都会警告您缺少一种情况。如果大量使用它,可能会很烦人。在这种特定情况下,最好单独使用一个变量。
很棒的福克斯先生

@FantasticMrFox我根据经验同意100%。该编译器警告也很重要。我已经发布了一种替代方法,更符合您的建议精神。
arr_sea

26

对于C ++ 17,您可以magic_enum::enum_count从lib https://github.com/Neargye/magic_enum使用:

magic_enum::enum_count<Example>() -> 4。

缺点在哪里?

该库使用特定于编译器的黑客(基于__PRETTY_FUNCTION__/ __FUNCSIG__),可在Clang> = 5,MSVC> = 15.3和GCC> = 9上工作。

我们遍历给定的间隔范围,并找到所有带有名称的枚举,这将是它们的计数。阅读有关限制的更多信息

有关此黑客的更多信息,请参见https://taylorconor.com/blog/enum-reflection


2
这太棒了!无需修改现有代码即可计算枚举成员的数量。同样,这似乎实现得非常优雅(只是略过了代码)!
andreee

通常不鼓励仅链接的答案。您能否用对库使用的技术的描述来扩展它?
阿德里安·麦卡锡

24
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;

这是从UglyCoder的答案中得出的,但是通过三种方式对其进行了改进。

  • type_safe枚举(BEGINSIZE)中没有多余的元素(Cameron的答案也有此问题。)
    • 编译器不会抱怨它们在switch语句中丢失(一个重大问题)
    • 它们不能无意间传递给期望您枚举的函数。(不是常见问题)
  • 它不需要铸造即可使用。(卡梅伦的答案也有这个问题。)
  • 减法不会影响枚举类类型的大小。

Cameron的答案相比,它保留了UglyCoder的优势,即枚举数可以分配任意值。

问题(与UglyCoder共享但与Cameron共享)是它使换行符和注释变得重要……这是意外的。因此,有人可以添加带有空格或注释的条目,而无需调整TEST_SIZE的计算。


7
enum class TEST
{
    BEGIN = __LINE__
    , ONE
    , TWO
    , NUMBER = __LINE__ - BEGIN - 1
};

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;

聪明!当然,不能有任何注释或不寻常的间距,并且对于非常大的源文件,基础值类型可能比其他情况下更大。
Kyle Strand'9

@Kyle Strand:存在一个问题:使用char时,您也有256个以上的枚举数。但是编译器有很好的方式通知您截断等信息。LINE是整数文字,使用#line的限制为[
1,2147483647

啊好吧。尽管如此,即使是一个枚举,也short可能会被破坏,int例如在进行统一构建时。(不过,我认为这是统一构建的问题,而不是您建议的技巧。)
Kyle Strand

招?:-)我使用它,但很少且需要适当的判断。像编码中的所有内容一样,我们需要提高优缺点,尤其是长期维护的影响。我最近使用它从C #defines(OpenGL wglExt.h)的列表中创建一个枚举类。
UglyCoder

5

有一个基于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
...

通过从中删除逗号#define MyEnumDef(并将其放入#define X(val) val),可以使此操作变得更加容易,这使您可以使用just来计数元素的数量#define X(val) +1 constexpr std::size_t len = MyEnumDef;
HolyBlackCat

4

您可以尝试的一种技巧是在列表的末尾添加一个枚举值,并将其用作大小。在你的例子中

enum class Example { A, B, C, D, E, ExampleCount };

与plainenum的行为相比,这不会像ExampleCounttype那样起作用Example。要获取中的元素数量ExampleExampleCount必须将其转换为整数类型。
applesoup

3

如果您使用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;

关于升压预处理器工具,这只是擦伤表面。例如,您的宏还可以为强类型枚举定义往返字符串转换实用程序和ostream运算符。

有关升压预处理器工具的更多信息,请访问:https : //www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


顺便说一句,我碰巧非常同意@FantasticMrFox,即Count如果使用switch语句,那么在接受的答案中使用的其他枚举值将引起编译器警告,令人头疼。我发现unhandled case编译器警告对于更安全的代码维护非常有用,因此我不想破坏它。


@FantasticMrFox感谢您指出所接受答案的相关问题。在这里,我提供了一种替代方法,它与您的建议的精神更加一致。
arr_sea

2

可以通过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;
}

2

还有另一种不依赖行数或模板的方式。唯一的要求是将枚举值粘贴在自己的文件中,并使预处理器/编译器进行计数,如下所示:

my_enum_inc.h

ENUMVAL(BANANA)
ENUMVAL(ORANGE=10)
ENUMVAL(KIWI)
...
#undef ENUMVAL

my_enum.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每次使用后都需要。


1

对此的另一种“愚蠢”解决方案是:

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您进行编译,请确保在忽略或重复任何开关大小写的情况下得到编译器警告。它也是constexpr,因此在编译时进行计算。

但是请注意,即使对于en而言enum class,即使枚举的第一个值不为0,默认的初始化值也为0。因此,您必须从0开始或显式使用第一个值。


0

不,您必须在代码中编写它。


0

您也可以考虑static_cast<int>(Example::E) + 1消除多余的元素。


8
对于这个特定的编程问题,此答案是正确的,但总的来说,它远非优雅和容易出错。将来可以用新值扩展枚举,这些新值可以替换Example::E为枚举中的最后一个值。即使不是这种情况,其Example::E字面值也可能会更改。
马提亚斯

0

反射TS:枚举(和其他类型)的静态反射

Reflection TS,尤其是Reflection 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是对象序列中元素的数量。

因此,在Reflection TS以其当前形式被接受和实现时,可以在编译时计算枚举的元素数,如下所示:

enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators<Example>::type;

static_assert(get_size<ExampleEnumerators>::value == 5U, "");

在这里我们可能会看到别名模板get_enumerators_vget_type_v进一步简化反射:

enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators_t<Example>;

static_assert(get_size_v<ExampleEnumerators> == 5U, "");

反思TS的状态

正如Herb Sutter的旅行报告: 2018年6月9日夏季ISO C ++委员会夏季会议的夏季ISO C ++标准会议(Rapperswil)所述,Reflection TS已被宣布为功能完善的

Reflection TS已完成功能:Reflection TS已被宣布为功能已完成,并将在今年夏天进行主要评论投票。再次注意,TS当前基于模板的基于元编程的语法只是一个占位符;所要求的反馈意见是在设计的核心“要点”上,并且委员会已经知道打算用一种简单的编程模型代替表面语法,该模型使用普通的编译时代码而不是<>样式化元编程。

初步刨为C ++ 20,但它有点不清楚,如果反射TS仍然有机会使其进入C ++ 20日发布。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.