【问题标题】:How to check if enum value is valid?如何检查枚举值是否有效?
【发布时间】:2011-02-11 12:55:51
【问题描述】:

我正在从二进制文件中读取enum 值,并想检查该值是否真的是enum 值的一部分。我该怎么做?

#include <iostream>

enum Abc
{
    A = 4,
    B = 8,
    C = 12
};

int main()
{
    int v1 = 4;
    Abc v2 = static_cast< Abc >( v1 );

    switch ( v2 )
    {
        case A:
            std::cout<<"A"<<std::endl;
            break;
        case B:
            std::cout<<"B"<<std::endl;
            break;
        case C:
            std::cout<<"C"<<std::endl;
            break;
        default :
            std::cout<<"no match found"<<std::endl;
    }
}

我必须使用switch 运算符还是有更好的方法?

编辑

我设置了枚举值,不幸的是我无法修改它们。更糟糕的是,它们不是连续的(它们的值是 0、75、76、80、85、90、95、100 等)

【问题讨论】:

  • 任何枚举都只是一个数字,所以我认为没有更好的方法来检查它。您可能应该为您的数据类型定义一个更严格的结构。

标签: c++ enums


【解决方案1】:

enum 值在 C++ 中有效,如果它落在范围 [A, B] 中,由下面的标准规则定义。所以在enum X { A = 1, B = 3 }的情况下,2的值被认为是一个有效的枚举值。

考虑标准的 7.2/6:

对于其中 emin 是最小枚举数且 emax 是最大枚举数的枚举,枚举的值是 bmin 到 bmax 范围内的基础类型的值,其中 bmin 和 bmax 分别是最小值的最小值和最大值可以存储 emin 和 emax 的位域。可以定义一个枚举,它的任何枚举器都没有定义它的值。

C++ 中没有追溯。一种方法是在数组中额外列出枚举值,并编写一个包装器来进行转换,并可能在失败时抛出异常。

有关如何将 int 转换为 enum 的详细信息,请参阅 Similar Question

【讨论】:

  • 您误解了标准报价,有效值中有多个[A,B]
  • 确实如此,例如如果值为 1 和 5,则后者至少需要 3 位,因此 6 和 7 也将是枚举数的有效值。
  • 这个答案仍然不正确。 @visitor 指出了您的示例失败的示例。他说“如果值是 1 和 5,那么后者至少需要 3 位,因此 6 和 7 也将是枚举数的有效值”。您的回答意味着在这种情况下,只有 {1, 2, 3, 4, 5} 是有效值。您对标准的引用是正确的,但您的示例具有误导性。
【解决方案2】:

也许像这样使用枚举:

enum MyEnum
{
A,
B,
C
};

检查

if (v2 >= A && v2 <= C)

如果您没有为枚举常量指定值,则这些值从零开始,并随着列表的每次向下移动而增加一。例如,给定 enum MyEnumType { ALPHA, BETA, GAMMA }; ALPHA 的值为 0,BETA 的值为 1,GAMMA 的值为 2。

【讨论】:

  • 我喜欢它的简单性,并通过始终将枚举中的第一项定义为 SOMETYPE_UNKNOWN 并将最后一项定义为 SOMETYPE_MAX 来扩展它。那么测试将始终为 AssertTrue(v2 >= SOMETYPE_UNKNOWN && v2
【解决方案3】:

如果您准备将枚举值作为模板参数列出,那么在 C++ 11 中有更好的方法。您可以将其视为一件好事,允许您在不同的上下文中接受有效枚举值的子集;在解析来自外部源的代码时通常很有用。

以下示例的一个可能有用的补充是围绕 EnumType 的基础类型相对于 IntType 的一些静态断言,以避免截断问题。留作练习。

#include <stdio.h>

template<typename EnumType, EnumType... Values> class EnumCheck;

template<typename EnumType> class EnumCheck<EnumType>
{
public:
    template<typename IntType>
    static bool constexpr is_value(IntType) { return false; }
};

template<typename EnumType, EnumType V, EnumType... Next>
class EnumCheck<EnumType, V, Next...> : private EnumCheck<EnumType, Next...>
{
    using super = EnumCheck<EnumType, Next...>;

public:
    template<typename IntType>
    static bool constexpr is_value(IntType v)
    {
        return v == static_cast<IntType>(V) || super::is_value(v);
    }
};

enum class Test {
    A = 1,
    C = 3,
    E = 5
};

using TestCheck = EnumCheck<Test, Test::A, Test::C, Test::E>;

void check_value(int v)
{
    if (TestCheck::is_value(v))
        printf("%d is OK\n", v);
    else
        printf("%d is not OK\n", v);
}

int main()
{
    for (int i = 0; i < 10; ++i)
        check_value(i);
}

【讨论】:

  • 因为 'int v' 的值在编译时未知,is_value 必须在运行时执行。与简单的 switch 语句或所有值的数组相比,这不会导致各种递归函数调用并且效率非常低吗?您仍然必须列出所有枚举值,因此您不会通过这种方法获得任何收益。还是我在这里错过了什么?
  • @InnocentBystander 都是constexpr 函数,所以编译器有很大的优化空间。这些函数也不是递归的;它是一个恰好具有相同名称的函数链。在上面示例的一些快速测试中,gcc 5.4 为模板版本与开关版本生成了更短的代码一条指令。 Clang 3.8 是模板版本的两个指令。结果将根据值的数量以及这些值是否连续而有所不同。最大的胜利,尤其是在进行协议解码时,是您将您期望的代码写在一行上。
  • 你是对的——抱歉不是“递归”本身,而是函数调用链。有趣的是,编译器可以优化所有这些。并感谢您跟进 3 岁的答案:)
【解决方案4】:

我发现让它变得“简单”的唯一方法是创建(宏)枚举的排序数组并检查它。

switch 技巧因enums 而失败,因为enum 可能有多个具有给定值的枚举数。

这是一个烦人的问题,真的。

【讨论】:

    【解决方案5】:

    C++ 托管扩展支持以下语法:

    enum Abc
    {
        A = 4,
        B = 8,
        C = 12
    };
    
    Enum::IsDefined(Abc::typeid, 8);
    

    参考:MSDN“Managed Extensions for C++ Programming

    【讨论】:

    • 我不确定“托管 c++”是什么,但你确定它是 c++,而不是 c#? this 看起来像 c#
    • @BЈовић: managed c++c++ 的Microsoft 变体,它能够使用.NET framework 的库。看起来这是c++,因为:: 运算符没有像这样在c# 中定义。
    • @BЈовић 您是否尝试过托管扩展 C++ 项目中的代码?我们在我们的一个 C++ 项目中使用了类似的代码。具体来说,我们使用 Enum::IsDefined() 方法。
    • @Brett 抱歉,我不知道“托管扩展 C++ 项目是什么”
    • @BЈовић 我在答案中添加了对 MSDN“C++ 编程的托管扩展”的引用。希望对您有所帮助。
    【解决方案6】:

    有点 necro,但是 ... 对 int 进行 RANGE 检查到第一个/最后一个枚举值(可以结合 janm 的想法进行精确检查),C++11:

    标题:

    namespace chkenum
    {
        template <class T, T begin, T end>
        struct RangeCheck
        {
        private:
            typedef typename std::underlying_type<T>::type val_t;
        public:
            static
            typename std::enable_if<std::is_enum<T>::value, bool>::type
            inrange(val_t value)
            {
                return value >= static_cast<val_t>(begin) && value <= static_cast<val_t>(end);
            }
        };
    
        template<class T>
        struct EnumCheck;
    }
    
    #define DECLARE_ENUM_CHECK(T,B,E) namespace chkenum {template<> struct EnumCheck<T> : public RangeCheck<T, B, E> {};}
    
    template<class T>
    inline
    typename std::enable_if<std::is_enum<T>::value, bool>::type
    testEnumRange(int val)
    {
        return chkenum::EnumCheck<T>::inrange(val);
    }
    

    枚举声明:

    enum MinMaxType
    {
         Max = 0x800, Min, Equal
    };
    DECLARE_ENUM_CHECK(MinMaxType, MinMaxType::Max, MinMaxType::Equal);
    

    用法:

    bool r = testEnumRange<MinMaxType>(i);
    

    上面提出的主要区别在于测试函数仅依赖于枚举类型本身。

    【讨论】:

      【解决方案7】:

      谈到一种语言,没有更好的方法,枚举值仅存在于编译时,没有办法以编程方式枚举它们。但是,使用经过深思熟虑的基础架构,您仍然可以避免多次列出所有值。见Easy way to use variables of enum types as string in C?

      然后可以使用此处提供的“enumFactory.h”重写您的示例:

      #include "enumFactory.h"
      
      #define ABC_ENUM(XX) \
          XX(A,=4) \
          XX(B,=8) \
          XX(C,=12) \
      
      DECLARE_ENUM(Abc,ABC_ENUM)
      
      int main()
      {
          int v1 = 4;
          Abc v2 = static_cast< Abc >( v1 );
      
          #define CHECK_ENUM_CASE(name,assign) case name: std::cout<< #name <<std::endl; break;
          switch ( v2 )
          {
              ABC_ENUM(CHECK_ENUM_CASE)
              default :
                  std::cout<<"no match found"<<std::endl;
          }
          #undef CHECK_ENUM_CASE
      }
      

      甚至(使用该标头中已经存在的更多工具):

      #include "enumFactory.h"
      
      #define ABC_ENUM(XX) \
          XX(A,=4) \
          XX(B,=8) \
          XX(C,=12) \
      
      DECLARE_ENUM(Abc,ABC_ENUM)
      DEFINE_ENUM(Abc,ABC_ENUM)
      
      int main()
      {
          int v1 = 4;
          Abc v2 = static_cast< Abc >( v1 );
          const char *name = GetString(v2);
          if (name[0]==0) name = "no match found";
          std::cout << name << std::endl;
      }
      

      【讨论】:

        【解决方案8】:

        一种无需查找的可能解决方案 - 枚举值应该是素数

        这个解决方案并不通用:

        • 不适用于标志枚举
        • 不适用于大枚举或大枚举值(溢出)
        • 可能难以保持一致性

        但实用:

        • O(1) 复杂度

        • 能够添加 INVALID=1 作为错误指示的枚举定义

        代码:

        #include<iostream>
        #include <cstdint>
        #include <vector>
        enum class Some :uint64_t{
           A=2,
           B=3,
           C=5,
           D=7,
           E=11,
           F=13,
        // etc. just stick to primes
        };
        static constexpr uint64_t some_checksum = static_cast<uint64_t>(Some::A)*
        static_cast<uint64_t>(Some::B)*
        static_cast<uint64_t>(Some::C)*
        static_cast<uint64_t>(Some::D)*
        static_cast<uint64_t>(Some::E)*
        static_cast<uint64_t>(Some::F);
        
        constexpr bool is_some(uint64_t v) {
            return some_checksum % v == 0;
        }
        constexpr bool get_some(uint64_t v, Some& out){
            if (some_checksum % v == 0) {
                out = static_cast<Some>(v);
                return true;
            }
            return false;//Something to indicate an error;
        }
        
        int main(int v) {
            Some s;
            if (get_some(v, s)){
                std::cout << "Ok\n" << static_cast<int>(s) << "\n"; 
            } else {
                std::cout << "No\n";
            }
        
        }
        

        【讨论】:

          【解决方案9】:

          另一种方法:

          #include <algorithm>
          #include <iterator>
          #include <iostream>
          
          template<typename>
          struct enum_traits { static constexpr void* values = nullptr; };
          
          namespace detail
          {
          
          template<typename T>
          constexpr bool is_value_of(int, void*) { return false; }
          
          template<typename T, typename U>
          constexpr bool is_value_of(int v, U)
          {
              using std::begin; using std::end;
          
              return std::find_if(begin(enum_traits<T>::values), end(enum_traits<T>::values),
                  [=](auto value){ return value == static_cast<T>(v); }
              ) != end(enum_traits<T>::values);
          }
          
          }
          
          template<typename T>
          constexpr bool is_value_of(int v)
          { return detail::is_value_of<T>(v, decltype(enum_traits<T>::values) { }); }
          
          ////////////////////
          enum Abc { A = 4, B = 8, C = 12 };
          
          template<>
          struct enum_traits<Abc> { static constexpr auto values = { A, B, C }; };
          decltype(enum_traits<Abc>::values) enum_traits<Abc>::values;
          
          enum class Def { D = 1, E = 3, F = 5 };
          
          int main()
          {
              std::cout << "Abc:";
              for(int i = 0; i < 10; ++i)
                  if(is_value_of<Abc>(i)) std::cout << " " << i;
              std::cout << std::endl;
          
              std::cout << "Def:";
              for(int i = 0; i < 10; ++i)
                  if(is_value_of<Def>(i)) std::cout << " " << i;
              std::cout << std::endl;
          
              return 0;
          }
          

          恕我直言,这种方法的“丑陋”部分必须定义:

          decltype(enum_traits<Abc>::values) enum_traits<Abc>::values
          

          如果你不反对宏,你可以把它包裹在一个宏里面:

          #define REGISTER_ENUM_VALUES(name, ...) \
          template<> struct enum_traits<name> { static constexpr auto values = { __VA_ARGS__ }; }; \
          decltype(enum_traits<name>::values) enum_traits<name>::values;
          

          【讨论】:

            【解决方案10】:

            使用C++17 的用户的另一个选择是使用fold expressions

            template<typename T, typename ...Args>
            constexpr typename std::enable_if_t<std::conjunction_v<std::is_same<T, Args>...>, std::optional<T>>
            ToOneOf( typename std::underlying_type_t<T> value, Args&& ...args ) noexcept
            {
                static_assert( std::is_enum_v<T>, "'T' must be of type enum." );
            
                using U = typename std::underlying_type_t<T>;
                std::array<T, sizeof...( Args )> values{ std::forward<Args>( args )... };
            
                const auto it{ std::find_if( std::cbegin( values ), std::cend( values ), 
                    [ value ]( auto e ) { return static_cast<U>( e ) == value; } ) };
            
                return it != std::end( values ) ? std::optional<T>{ *it } : std::nullopt;
            }   
            
            template<typename T, typename ...Args>
            constexpr typename std::enable_if_t<std::conjunction_v<std::is_same<T, Args>...>, bool>
            IsOneOf( typename std::underlying_type_t<T> value, Args&& ...args ) noexcept
            {
                static_assert( std::is_enum_v<T>, "'T' must be of type enum." );
            
                using U = typename std::underlying_type_t<T>;
                return ( ... || ( value == static_cast<U>( args ) ) );
            }
            
            enum class Test
            {
                E0 = 12,
                E1 = 56,
                E2 = 101
            };
            
            int main( )
            {
                if ( IsOneOf<Test>( 12, Test::E0, Test::E1, Test::E2 ) )
                {
                    std::cout << 12 << " is a valid enum value\n";
                }
            
                if ( auto opt{ ToOneOf<Test>( 56, Test::E0, Test::E1, Test::E2 ) } )
                {
                     std::cout << static_cast<int>( opt.value( ) )  << " is a valid enum value\n";
                }
            }
            

            【讨论】:

            • 仍然需要复制枚举。您想要的是能够扩展枚举,并且其他所有内容都可以在不修改的情况下继续工作。在这方面,这个例子实际上比简单的switch 更糟糕,因为使用switch 你至少可以在没有处理所有枚举值情况时生成警告。
            • IsOneOf 只检查 12 是否是此处的 3 个命名值之一。 enum 也可以保存没有名称的值。特别是,0 始终是合法的。找到那些未命名但合法的价值观是个难题。
            • @MSalters 我相信这是我们想要的。 OP 希望确保读取的值是 enum 中明确指定的变体之一。
            猜你喜欢
            • 1970-01-01
            • 2015-02-12
            • 2019-12-29
            • 2014-09-19
            • 1970-01-01
            • 1970-01-01
            • 2012-02-16
            • 2021-05-12
            相关资源
            最近更新 更多