【问题标题】:Generic way to cast int to enum in C++在 C++ 中将 int 转换为枚举的通用方法
【发布时间】:2011-05-09 02:29:07
【问题描述】:

有没有一种通用的方法将int 转换为enumC++ 中?

如果intenum 的范围内,它应该返回一个enum 值,否则抛出一个exception。有没有办法一般地写?应该支持多个enum type

背景:我有一个外部 enum 类型并且无法控制源代码。我想将此值存储在数据库中并检索它。

【问题讨论】:

  • enum e{x = 10000}; 在这种情况下 9999 是否在 enum 的范围内?
  • 不,9999 不会倒下。
  • 好问题。至于任何“为什么?”这将出现,让我说“反序列化” - 对我来说似乎足够了。我也很高兴听到 enum class 的 C++0x 编译答案。
  • “范围”在这里是错误的词,可能是“域”?
  • boost::numeric_cast 如果值超出范围,则抛出正或负溢出异常。但不确定它是否也适用于枚举类型。你可以试试。

标签: c++ casting enums


【解决方案1】:

显而易见的事情是注释你的枚举:

// generic code
#include <algorithm>

template <typename T>
struct enum_traits {};

template<typename T, size_t N>
T *endof(T (&ra)[N]) {
    return ra + N;
}

template<typename T, typename ValType>
T check(ValType v) {
    typedef enum_traits<T> traits;
    const T *first = traits::enumerators;
    const T *last = endof(traits::enumerators);
    if (traits::sorted) { // probably premature optimization
        if (std::binary_search(first, last, v)) return T(v);
    } else if (std::find(first, last, v) != last) {
        return T(v);
    }
    throw "exception";
}

// "enhanced" definition of enum
enum e {
    x = 1,
    y = 4,
    z = 10,
};

template<>
struct enum_traits<e> {
    static const e enumerators[];
    static const bool sorted = true;
};
// must appear in only one TU,
// so if the above is in a header then it will need the array size
const e enum_traits<e>::enumerators[] = {x, y, z};

// usage
int main() {
    e good = check<e>(1);
    e bad = check<e>(2);
}

您需要将数组与e 保持同步,如果您不是e 的作者,这会很麻烦。正如 Sjoerd 所说,它可能可以通过任何体面的构建系统实现自动化。

无论如何,你的对手是 7.2/6:

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

因此,如果您不是e 的作者,您可能无法保证e 的有效值实际出现在其定义中。

【讨论】:

    【解决方案2】:

    丑。

    enum MyEnum { one = 1, two = 2 };
    
    MyEnum to_enum(int n)
    {
      switch( n )
      {
        case 1 :  return one;
        case 2 : return two;
      }
      throw something();
    }
    

    现在是真正的问题。你为什么需要这个?代码很丑陋,不容易编写(*?),不容易维护,也不容易合并到你的代码中。它告诉你它是错误的代码。为什么要打呢?

    编辑:

    或者,假设枚举是 C++ 中的整数类型:

    enum my_enum_val = static_cast<MyEnum>(my_int_val);
    

    但这比上面的更难看,更容易出错,而且不会像你想要的那样抛出。

    【讨论】:

    • 这只支持一种类型,MyEnum。
    • @Leonid:据我所知,一般情况下无法做到这一点。在某种程度上,你想出的任何解决方案都会throw(或做任何特殊的事情)来处理无效类型,必须像我发布的那样有一个开关。
    • 为什么这是-1'ed?这是正确答案。仅仅因为这不是一些人所希望的答案并不意味着它是错误的。
    • static_cast&lt;MyEnum&gt; 也可以,并且应该优先于 reinterpret_cast&lt;MyEnum&gt;
    • 这种方法效果很好,如果使用工具生成函数效果会更好。
    【解决方案3】:

    如果如您所述,值在数据库中,为什么不编写一个代码生成器来读取此表并创建一个包含枚举和 to_enum(int) 函数的 .h 和 .cpp 文件?

    优点:

    • 轻松添加to_string(my_enum) 函数。
    • 几乎不需要维护
    • 数据库和代码同步

    【讨论】:

    • 无法控制 enum 类型的源代码。我同意可以生成实现转换的设施。但是,该工具不会知道对外部 enum 类型所做的任何更改/扩展(除非每次在编译时执行)。
    • @Leonid 然后读取那个枚举头并基于它生成to_enum(int)函数。
    • @Leonid 每个严肃的项目管理系统,甚至是make,都可以比较两个文件的日期,看看是否需要重新运行生成器。
    • 我想我现在将采用更简单的生成器解决方案。但是感谢您的想法。
    • 我们在工作场所使用这个方案,一个工具从模板生成.hpp代码,模板是最小的。
    【解决方案4】:

    不,C++ 中没有内省,也没有任何内置的“域检查”工具。

    【讨论】:

      【解决方案5】:

      你觉得这个怎么样?

      #include <iostream>
      #include <stdexcept>
      #include <set>
      #include <string>
      
      using namespace std;
      
      template<typename T>
      class Enum
      {
      public:
          static void insert(int value)
          {
              _set.insert(value);
          }
      
          static T buildFrom(int value)
          {
              if (_set.find(value) != _set.end()) {
                  T retval;
                  retval.assign(value);
                  return retval;
              }
              throw std::runtime_error("unexpected value");
          }
      
          operator int() const { return _value; }
      
      private:
          void assign(int value)
          {
              _value = value;
          }
      
          int _value;
          static std::set<int> _set;
      };
      
      template<typename T> std::set<int> Enum<T>::_set;
      
      class Apples: public Enum<Apples> {};
      
      class Oranges: public Enum<Oranges> {};
      
      class Proxy
      {
      public:
          Proxy(int value): _value(value) {}
      
          template<typename T>
          operator T()
          {
              T theEnum;
              return theEnum.buildFrom(_value);
          }
      
          int _value;
      };
      
      Proxy convert(int value)
      {
          return Proxy(value);
      }
      
      int main()
      {    
          Apples::insert(4);
          Apples::insert(8);
      
          Apples a = convert(4); // works
          std::cout << a << std::endl; // prints 4
      
          try {
              Apples b = convert(9); // throws    
          }
          catch (std::exception const& e) {
              std::cout << e.what() << std::endl; // prints "unexpected value"
          }
          try {
              Oranges b = convert(4); // also throws  
          }
          catch (std::exception const& e) {
              std::cout << e.what() << std::endl; // prints "unexpected value"
          }
      }
      

      然后您可以使用我发布的代码 here 来打开值。

      【讨论】:

      • 您仍然需要在某处添加Apples::insert(4),因此这与开关相比没有优势。
      • 他说值来自数据库,所以我添加了一个“插入”方法。
      【解决方案6】:

      您不应该希望存在您所描述的内容,我担心您的代码设计存在问题。

      此外,您假设枚举在一个范围内,但情况并非总是如此:

      enum Flags { one = 1, two = 2, four = 4, eigh = 8, big = 2000000000 };
      

      这不在一个范围内:即使有可能,您是否应该检查从 0 到 2^n 的每个整数以查看它们是否与某个枚举的值匹配?

      【讨论】:

      • 您将如何从数据库中检索枚举值?整数在编译时是已知的,那么为什么不能基于模板进行通用转换?
      • @Leonid:因为在某种程度上,你需要有一个开关,就像我说的那样。
      • @Leonid 模板并不是解决您能想到的所有问题的灵丹妙药。
      • 约翰是对的。你需要一个比枚举更复杂的类型来做你想做的事,我认为使用类层次结构是可行的。
      • 我发布了一个使用类层次结构的解决方案,看看吧。
      【解决方案7】:

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

      也许没有你想的那么通用,但检查代码本身是通用的,你只需要指定一组值。这种方法可以处理间隙、任意值等。

      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<typename std::underlying_type<EnumType>::type>(V) || super::is_value(v);
          }
      
          EnumType convert(IntType v)
          {
              if (!is_value(v)) throw std::runtime_error("Enum value out of range");
              return static_cast<EnumType>(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);
      }
      

      【讨论】:

      • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
      • @Tas 这是一个指向不同 SO 答案的链接——它没有与外部链接相同的问题。反正更新了。
      【解决方案8】:

      C++0x 替代“丑陋”版本,允许多个枚举。使用初始化列表而不是开关,更简洁的 IMO。不幸的是,这不能解决对枚举值进行硬编码的需要。

      #include <cassert>  // assert
      
      namespace  // unnamed namespace
      {
          enum class e1 { value_1 = 1, value_2 = 2 };
          enum class e2 { value_3 = 3, value_4 = 4 };
      
          template <typename T>
          int valid_enum( const int val, const T& vec )
          {
              for ( const auto item : vec )
                  if ( static_cast<int>( item ) == val ) return val;
      
              throw std::exception( "invalid enum value!" );  // throw something useful here
          }   // valid_enum
      }   // ns
      
      int main()
      {
          // generate list of valid values
          const auto e1_valid_values = { e1::value_1, e1::value_2 };
          const auto e2_valid_values = { e2::value_3, e2::value_4 };
      
          auto result1 = static_cast<e1>( valid_enum( 1, e1_valid_values ) );
          assert( result1 == e1::value_1 );
      
          auto result2 = static_cast<e2>( valid_enum( 3, e2_valid_values ) );
          assert( result2 == e2::value_3 );
      
          // test throw on invalid value
          try
          {
              auto result3 = static_cast<e1>( valid_enum( 9999999, e1_valid_values ) );
              assert( false );
          }
          catch ( ... )
          {
              assert( true );
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-02
        • 1970-01-01
        • 2012-07-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多