【问题标题】:Is it possible to determine the number of elements of a c++ enum class?是否可以确定 c++ 枚举类的元素数量?
【发布时间】:2013-02-20 20:27:11
【问题描述】:

是否可以确定 c++ enum class 的基数:

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

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

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

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

【问题讨论】:

  • 我认为可能存在特定的 c++ 11 机制
  • 顺便说一句,这不是重复的。 enumenum classes 是非常不同的概念。
  • @Shoe ...他们真的吗?
  • 这似乎是一个 XY 问题,我知道这是很久以前的问题了,但你还记得你为什么需要这样做吗?你不能迭代 enum class 值,那么知道这个数字有什么好处呢?
  • @FantasticMrFox 可能不是 OP 的原因,但一个示例是将数组初始化为枚举的“大小”,然后使用枚举器索引数组。我怀疑还有更多。

标签: c++ c++11 cardinality enum-class


【解决方案1】:

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

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 作为枚举值的参数——所以使用它时要小心! (不过,我个人认为这在实践中不是问题。)

【讨论】:

  • 枚举值在枚举类中是类型安全的,所以'Count'在这里的类型是 Example 而不是 int,对吗?您必须先将 'Count' 转换为 int 才能使用它的大小。
  • @Man:是的,使用enum classes 而不是普通的enums,这个技巧有点混乱。为了清楚起见,我将在演员表中进行编辑。
  • 如果你在这个枚举中使用 switch 语句,任何体面的编译器都会警告你你错过了一个案例。如果经常使用这可能会很烦人。在这种特定情况下,最好只使用一个单独的变量。
  • @FantasticMrFox 我同意 100%,根据经验。该编译器警告也是一个重要的警告。我发布了另一种方法,更符合您的建议精神。
【解决方案2】:

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

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

缺点在哪里?

此库使用编译器特定的 hack(基于 __PRETTY_FUNCTION__ / __FUNCSIG__),适用于 Clang >= 5、MSVC >= 15.3 和 GCC >= 9。

我们遍历给定的区间范围,并找到所有具有名称的枚举,这将是它们的计数。 阅读更多关于limitations

这篇文章https://taylorconor.com/blog/enum-reflection中有更多关于这个黑客的信息。

【讨论】:

  • 这太棒了!无需修改现有代码来计算枚举成员的数量。此外,这似乎非常优雅地实现(只是浏览了代码)!
  • 通常不鼓励仅链接的答案。您能否通过描述您的库使用的技术来扩展此内容?
  • 一个巨大的限制是枚举值被限制在小范围内(最多它们可以扩展到短范围,而不是 int 范围)。如果库失败,它不会因 static_assert 或编译时错误而失败,它只会“剪切”枚举值并返回枚举值的子集。并不是说它可以在 C++ 中做得更好,而是重要的限制。仍然赞成,您的库是我最喜欢的情况,我可以使用它。
  • 对编译时间的影响会很有趣,尤其是。范围更大。
  • 可能有优化潜力。不幸的是,我上次尝试时 clang 的 -ftime-trace 并没有报告太多 constexpr 代码:github.com/llvm/llvm-project/issues/42754
【解决方案3】:
// 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's answer,但在三个方面对其进行了改进。

  • type_safe 枚举中没有多余的元素(BEGINSIZE)(Cameron's answer 也有这个问题。)
    • 编译器不会抱怨 switch 语句中缺少它们(一个重大问题)
    • 不能将它们无意中传递给期望您的枚举的函数。 (不是常见问题)
  • 它不需要铸造就可以使用。 (Cameron's answer 也有这个问题。)
  • 减法不会影响枚举类类型的大小。

它保留了UglyCoder's 优于Cameron's answer 的优势,即可以为枚举数分配任意值。

一个问题(与UglyCoder 共享但与Cameron 不共享)是它使换行符和cmets 变得重要......这是出乎意料的。所以有人可以在不调整TEST_SIZE 的计算的情况下添加一个带有空格或注释的条目。这意味着代码格式化程序可以打破这一点。在evg656e's 发表评论后,我编辑了禁用clang-format 的答案,但如果您使用不同的格式化程序,请注意

【讨论】:

  • 像 clang-format 这样的工具也可以轻松破解这个问题
  • 谢谢@evg656e。我为clang-format 添加了一个解决方法,并明确提到了代码格式化程序作为添加空格问题的一部分。
【解决方案4】:
// 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;

【讨论】:

  • 聪明!当然,不能有任何 cmets 或不寻常的间距,对于非常大的源文件,基础值类型可能会比其他情况大。
  • @Kyle Strand:存在这个问题:使用 char 并且您也有超过 256 个枚举器。但是编译器有很好的方式来通知你截断等。 LINE 是一个整数文字,使用 #line 的限制为 [1, 2147483647]
  • 啊,好吧。尽管如此,即使是 short 的枚举也可能会被提升到 int,例如在进行统一构建时。 (不过,我想说这更多的是统一构建的问题,而不是您提出的技巧。)
  • 把戏? :-) 我使用它,但很少使用它,并且有适当的判断。就像编码中的一切一样,我们需要权衡利弊,尤其是长期维护的影响。我最近用它从 C#defines (OpenGL wglExt.h) 列表中创建了一个枚举类。
【解决方案5】:

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

【讨论】:

    【解决方案6】:

    有一个基于 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)来简化,这样您就可以只使用#define X(val) +1 constexpr std::size_t len = MyEnumDef; 来计算元素的数量。
    【解决方案7】:

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

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

    【讨论】:

    • 与普通enums 的行为相比,这将不起作用,因为ExampleCountExample 类型。要获取Example 中的元素数,必须将ExampleCount 强制转换为整数类型。
    【解决方案8】:

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

    Reflection TS,尤其是latest version of the Reflection TS draft的[reflect.ops.enum]/2提供get_enumeratorsTransformationTrait操作:

    [reflect.ops.enum]/2

    template <Enum T> struct get_enumerators
    

    get_enumerators&lt;T&gt; 的所有专业都应符合 TransformationTrait 要求 (20.10.1)。嵌套类型命名 type 指定满足的元对象类型 ObjectSequence,包含满足 Enumerator 和 反映T所反映的枚举类型的枚举数。

    草案的[reflect.ops.objseq] 涵盖ObjectSequence 操作,其中特别是[reflect.ops.objseq]/1 涵盖get_size 特征,用于提取满足@987654336 的元对象的元素数量@:

    [reflect.ops.objseq]/1

    template <ObjectSequence T> struct get_size;
    

    get_size&lt;T&gt; 的所有专业都应符合 UnaryTypeTrait 要求 (20.10.1) 具有基本特征 integral_constant&lt;size_t, N&gt;,其中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_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++ 委员会夏季会议上的 Trip report: Summer ISO C++ standards meeting (Rapperswil) 所述,Reflection TS 已被宣布为功能完备

    Reflection TS 功能完备:Reflection TS 被宣布为功能完备,并将在夏季进行主要评论投票。再次注意,TS 当前基于模板元编程的语法只是一个占位符;所要求的反馈是关于设计的核心“胆量”,委员会已经知道它打算用更简单的编程模型替换表面语法,该模型使用普通的编译时代码,而不是&lt;&gt; 风格的元编程。

    并且是 initially planed for C++20,但目前还不清楚 Reflection TS 是否仍有机会进入 C++20 版本。

    【讨论】:

      【解决方案9】:

      如果您使用 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 运算符。

      更多关于 boost 预处理器工具的信息: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


      顺便说一句,我碰巧非常同意@FantasticMrFox 的观点,即如果使用switch 语句,在接受的答案中使用的额外Count 枚举值将导致编译器警告问题。我发现unhandled case 编译器警告对于更安全的代码维护非常有用,所以我不想破坏它。

      【讨论】:

      • @FantasticMrFox 感谢您指出与接受的答案有关的问题。我在这里提供了一种更符合您的建议精神的替代方法。
      【解决方案10】:

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

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

      这允许您将 cmets 与枚举值一起放置、重新分配值,并且不会注入需要在代码中忽略/考虑的无效“计数”枚举值。

      如果您不关心 cmets,则不需要额外的文件,可以像上面提到的那样做,例如:

      #define MY_ENUM_LIST \
          ENUMVAL(BANANA) \
          ENUMVAL(ORANGE = 7) \
          ENUMVAL(KIWI)
      

      并用 MY_ENUM_LIST 替换 #include "my_enum_inc.h" 指令,但每次使用后都需要 #undef ENUMVAL

      【讨论】:

        【解决方案11】:

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

        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 所以这是在编译时计算的。

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

        【讨论】:

          【解决方案12】:

          不行,你得写在代码里。

          【讨论】:

            【解决方案13】:

            您也可以考虑static_cast&lt;int&gt;(Example::E) + 1,它消除了多余的元素。

            【讨论】:

            • 这个答案对于这个特定的编程问题是正确的,但总的来说远非优雅和容易出错。将来可以使用新值扩展枚举,这些新值可以替换 Example::E 作为枚举中的最后一个值。即使不是这种情况,Example::E 的字面值也可能会改变。
            【解决方案14】:

            这是在 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
            

            【讨论】:

              猜你喜欢
              • 2016-10-28
              • 2019-01-24
              • 2012-05-30
              • 2014-08-04
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多