【问题标题】:How do I switch over an enum class?如何切换枚举类?
【发布时间】:2020-08-09 03:39:58
【问题描述】:

枚举类应该是强枚举,因为它们不会隐式地与int 相互转换。例如:

enum class EC { a, b };

但是,当切换这样一个“强枚举”时:

int sw(EC ec) {
  switch (ec) {
    case EC::a: return 0;
    case EC::b: return 1;
  }
}

gcc -Wreturn-type-希望我在 switch 中添加一个 default 子句,即使涵盖了所有合法的枚举值:

warning: control reaches end of non-void function [-Wreturn-type]

在旧的(非类)枚举中,这是有道理的,因为任何int 都可能被意外转换为EC。但是我(显然错误地)假设将无效的枚举成员分配给枚举类是 UB。

当编译器意识到像sw 这样的函数覆盖所有可能的路径时,如何使用真正强大的枚举类?当然,我可以添加一个我知道永远不会触发的 default: 分支,但我想确保将来向 EC 添加更多成员触发警告在开关中

【问题讨论】:

  • 在我的代码中,我放了一个 should never occur throw std::logic_error("failure"); 而不是一个 should never occur 返回一个默认值或欺骗值。

标签: c++ switch-statement strong-typing enum-class defensive-programming


【解决方案1】:

您可以返回一个虚拟变量,以删除“控制到达非无效函数的结尾”。这样,警告就被移除了,对 enum-class 的任何添加仍然会在 switch 语句中触发警告:

int sw(EC ec) {
  switch (ec) {
    case EC::a: return 0;
    case EC::b: return 1;
  }

  return 0; //dummy variable
}

【讨论】:

  • 在 GCC 中你可以使用__builtin_unreachable();
  • @Evg 是的,但这不是标准行为。
  • @Jarod42 它可能根据上下文更合适。如果函数不是关键的,或者如果返回值被检查,或者使用异常是不可接受的等等,它可能更适合返回一个虚拟变量。没有任何进一步的信息,我无法提出任何强烈的建议。
【解决方案2】:

“控制到达非无效函数的结尾”与常见的“枚举值'c'未在开关[-Wswitch]中处理”警告完全不同。我认为编译器在这里有点过于谨慎,但这个警告可能会变得很方便,因为它将防止由于修改enum 和忽略-Wswitch 警告而导致的潜在未来UB。

像这样重写这个 sn-p 将使代码面向未来:

online compiler

enum class EC { a, b /*,c */ };

int sw(EC ec) {
    int result{};
  switch (ec) { // warning: enumeration value 'c' not handled in switch [-Wswitch]
    case EC::a: result = 0; break;
    case EC::b: result = 1; break;
  }
  return result; // control flow will always leave function properly
}

【讨论】:

  • 关于-Wswitch-Wreturn-type 的观点是有效的,我错过了。但是,这可能需要移动和分配结果(这对于非 int 类型可能不是一个好主意)。
【解决方案3】:

enum 变量 - old-school one 和 enum class 都可以保存不是枚举成员之一的值。只要整数值适合基础类型(还有一些限制),就可以将其存储在枚举类型中。

【讨论】:

  • 关于“只要整数值适合底层类型”的规则仅适用于显式定义底层类型时。在 OP 的示例中,EC 的唯一有效值是 0 和 1,即使它具有 int 作为底层类型。
  • @user7860670 我不认为你是对的。我看看能不能在标准中找到相关的文字。
  • 参见 10.2 枚举声明 [dcl.enum] 8 对于基础类型固定的枚举,枚举的值是基础类型的值。否则,对于其中 e min 是最小枚举数且 e max 是最大枚举数的枚举,枚举的值是 b min 到 b max 范围内的值,定义如下: 令 K 为 1 表示二进制补码,并且0 表示一个的补码或符号大小表示。 b max 是大于或等于 max ( |e min | − K,|e max | ) 且等于 2 M − 1 的最小值,其中 M 是非负数
  • @user7860670 : eel.is/c++draft/enum#dcl.enum "对于一个基础类型固定的枚举,枚举的值是基础类型的值。否则,枚举的值是一个可以表示的值具有最小宽度 M 的假设整数类型,以便可以表示所有枚举数。足以容纳枚举类型的所有值的最小位域的宽度是 M。可以定义具有未定义值的枚举它的任何枚举器。” - 因为只有 2 个值为 0 和 1 的枚举器,所以你是对的
  • @user7860670 但是对于具有更大 M 的更大枚举,很容易在列出的枚举数之外获得值。
【解决方案4】:

这对我有用:

enum class EC {a, b, c};
int sw (EC ec)
{
    int rc;
    switch (ec)
    {
        case EC::a:
        rc = 0;
        break;
    case EC::b:
        rc = 1;
        break;
    }
    return rc;
}

【讨论】:

    【解决方案5】:

    在 GCC/Clang/ICC 中,您可以使用 __builtin_unreachable() 消除此警告:

    int sw(EC ec) {
        switch (ec) {
            case EC::a: return 0;
            case EC::b: return 1;
        }
    
        assert(false);
        __builtin_unreachable();
    }
    

    在MSVC中__assume(0)可以使用。

    【讨论】:

      猜你喜欢
      • 2014-11-10
      • 1970-01-01
      • 2011-10-28
      • 1970-01-01
      • 2016-02-27
      • 2023-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多