【问题标题】:How to safely cast integral types to scoped enums如何安全地将整数类型转换为作用域枚举
【发布时间】:2016-06-10 15:31:25
【问题描述】:

C++11 范围枚举很棒,您应该尽可能使用它们。 但是,有时您需要将整数转换为作用域枚举值(例如,如果您从用户输入中获取它)。

是否有一种安全的方法来执行此操作并检测值何时无效(即超出枚举的允许值)?

如果整数无效,我相信仅使用 static_cast 会导致未定义的行为。是否有一种通用方法不需要手动为每个作用域枚举类型编写转换函数(并且每次向枚举添加新值时都需要更新)?

【问题讨论】:

  • 行为未定义。第 7.2 节第 10 节说“算术或枚举类型的表达式可以显式转换为枚举类型。如果它在枚举类型的枚举值范围内,则该值不变;否则生成的枚举值为未指定。”所以值是未指定的,但行为不是未定义的(没有鼻恶魔)。
  • @Cornstalks,感谢您指出。这绝对比未定义要好。尽管如此,问题仍然存在:是否有一种很好的方法来检测该值是否无效?
  • 不幸的是,我不知道有一个。
  • @Cornstalks 但是,在 C++17 中,行为将是未定义的:eel.is/c++draft/expr.static.cast#10
  • @ecatmur:请注意,“枚举值”比您列出的要多。特别是,对于范围枚举,该值可以是任何适合 int 的值。 (请参阅 T.C. 的回答;直到阅读它,我自己才知道这一点......)

标签: c++ c++11


【解决方案1】:

一种常见的方法是在您的枚举中包含一个结束标记

enum class Colors : char
{
  Red,
  Green,
  Blue,
  Last_Element
}

使用这种方法,在转换的时候,你可以检查你使用的值是否小于 Last_Element 的值。

考虑以下函数:

template<typename T>
typename std::enable_if<std::is_enum<T>::value, bool>::type
  IsValidEnumIntegral<T>(int integral)
{
  return (int)(T::Last_Element) > integral;
}

你可以像这样使用它:

if(IsValidEnumIntegral<Colors>(2))
  //do whatever

这适用于您使用名为 Last_Element 的元素创建的任何枚举。您可以进一步创建类似的功能,然后为您自动转换。

注意:这未经测试。我目前无法这样做,但我认为这可行。

编辑:这仅在所讨论的枚举使用一组整数且其元素没有间隙时才有效。提供的函数还将假定枚举不包含负整数,尽管可以轻松地将 First_Element 添加到其中。

【讨论】:

  • 这只有在enum 中没有漏洞的情况下才有效。如果有任何差距,这仍然可能失败。
  • @NathanOliver 没有完美的解决方案,你必须接受限制。
  • 如果您真的需要这样做,正确的方法是定义一个类,其构造函数会执行您想要的精确检查,而不是使用枚举。
  • @Barmar 我愿意。我只是想让它注意到。 (如果你认为我是,也不是 OP,听起来你认为我是)
  • @Altainia,感谢您的回答。我 99% 相信这是你能做的最好的,但会等待更长的时间才能接受以防万一。
【解决方案2】:

[dcl.enum]/8:

对于基础类型固定的枚举, 枚举是底层类型的值。

这包括所有范围枚举,因为范围枚举的基础类型默认为int

可以使用 enum-base 显式指定底层类型。 对于作用域枚举类型,如果不是,则基础类型为int 明确规定。在这两种情况下,基础类型都是 据说是固定的

因此,通过验证输入值是否在枚举的基础类型范围内(您可以使用std::numeric_limitsstd::underlying_type 进行检查),您可以确定static_cast 将始终具有良好的- 定义的行为。

但是,如果您的程序的其余部分没有准备好处理枚举基础类型范围内的每个值,这还不够。在这种情况下,您必须自己进行验证,可能与@Altainia 的回答类似。

【讨论】:

  • 我从来没有仔细阅读过枚举规范,这完全不是我所期望的。我今天学到了一些东西;谢谢。
  • 这是一个非常好的观点,误解了标准中“枚举值”的含义。感谢您指出!
【解决方案3】:

我在一些代码库中看到这种事情的另一种方式是,基本上,使用类型特征来装饰你的作用域枚举与额外的信息。

这个想法是你会有一些像

namespace mpl {
    template <typename T>
    struct GetEnumData;
}

然后,当您声明“智能”枚举时,您还将专门化此 trait 以链接到包含有关范围枚举的元数据的结构,例如合法值是什么。或者,与枚举值的名称对应的字符串列表,因此您可以进行枚举到字符串的转换并返回。

enum class my_enum { a, b, c };
namespace mpl {
  template<>
  struct GetEnumData<my_enum> {
    static constexpr std::size_t my_number = 3;
    static const char * const my_strings [] () {
      return {"a", "b", "c"};
    }
    static const int my_values [] () {
      return {0, 1, 2};
    }
  }
} // end namespace mpl

注意生成上述类型特征有点乏味,所以总是会为“智能枚举”声明使用一些宏。如果您真的不喜欢宏,那么这种方法不适合您。至少在 C++ 在未来的版本中添加更多的自省功能之前(祈祷)。

一旦你有了这种类型特征,你就可以做一些有用的事情,比如“enum_cast”,它将一个字符串解析为一个枚举。

template <typename T>
T enum_cast(const std::string & input) {
  using data = GetEnumData<T>;

  for (std::size_t idx = 0; idx < data::my_number; ++idx) {
    if (data::my_strings()[idx] == input) {
      return static_cast<T>(data::my_values()[idx]);
    }
  }
  throw bad_enum_value(input);
}

您可以对int 或其他整数类型执行类似的操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-04
    • 1970-01-01
    • 2015-01-25
    • 2012-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多