【问题标题】:Handling of switch enum class returns in clang, gcc and icc consistently一致地处理 clang、gcc 和 icc 中的开关枚举类返回
【发布时间】:2020-02-26 03:14:13
【问题描述】:

我通常使用clang 开发代码,尽我所能使用所有合理的警告 (-Wall -Wextra [-Wpedantic])。这个设置的好处之一是编译器检查switch stataments 与所使用的枚举的一致性。例如在这段代码中:

enum class E{e1, e2};

int fun(E e){
    switch(e){
        case E::e1: return 11;
        case E::e2: return 22; // if I forget this line, clang warns
    }
}

clang 会抱怨(警告)如果:我省略了e1e2 的情况,并且没有默认情况。

<source>:4:12: warning: enumeration value 'e2' not handled in switch [-Wswitch]
    switch(e){

这种行为很棒,因为

  1. 它在编译时检查枚举和开关之间的一致性,使它们成为非常有用且不可分割的一对功能。
  2. 我不需要定义一个人为的 default 案例,我不会为此做任何好事。
  3. 它允许我省略一个全局返回,我不会为它返回一个好东西(有时返回不是像int 这样的简单类型,例如它可能是没有默认构造函数的类型。

(请注意,我使用的是enum class,所以我只假设有效案例,因为无效案例只能由调用者端的讨厌演员生成。)

现在是个坏消息: 不幸的是,当切换到其他编译器时,这很快就会崩溃。 在 GCC 和 Intel (icc) 中,上面的代码警告(使用相同的标志)我没有从非 void 函数返回。

<source>: In function 'int fun(E)':
<source>:11:1: warning: control reaches end of non-void function [-Wreturn-type]
   11 | }
      | ^
Compiler returned: 0

我发现的唯一解决方案是同时具有 default 大小写并返回无意义的值。

int fun(E e){
    switch(e){
        case E::e1: return 11;
        case E::e2: return 22;
        default: return {}; // or int{} // needed by GCC and icc
    }
}

这很糟糕,因为我上面提到的原因(甚至没有达到返回类型没有默认构造函数的情况)。 但这也很糟糕,因为我可以再次忘记其中一个枚举案例,现在clang 不会抱怨,因为有一个默认案例。

所以我最终要做的是让这段丑陋的代码在这些编译器上运行,并在出于正确的原因发出警告。

enum E{e1, e2};

int fun(E e){
    switch(e){
        case E::e1: return 11;
        case E::e2: return 22;
#ifndef __clang__    
        default: return {};
#endif
    }
}

int fun(E e){
    switch(e){
        case E::e1: return 11;
        case E::e2: return 22;
    }
#ifndef __clang__    
    return {};
#endif
}

有更好的方法吗?

这是示例:https://godbolt.org/z/h5_HAs


非默认可构造类的情况下,我真的完全没有好的选择:

A fun(E e){
    switch(e){
        case E::e1: return A{11};
        case E::e2: return A{22};
    }
#ifndef __clang__
    return reinterpret_cast<A const&>(e);  // :P, because return A{} could be invalid
#endif
}

https://godbolt.org/z/3WC5v8

【问题讨论】:

    标签: c++ c++11 enums switch-statement enum-class


    【解决方案1】:

    请务必注意,鉴于您对 fun 的初始定义,完全合法的 C++ 执行以下操作:

    fun(static_cast<E>(2));
    

    任何枚举类型都可以在其表示的位数内采用任何值。具有显式基础类型(enum class always 具有基础类型;int 默认情况下)的表示是该基础类型的整体。因此,默认情况下enum class 可以假定任何int 的值。

    不是在 C++ 中未定义的行为。

    因此,GCC 完全有权假设 fun 可以获取其基础类型范围内的任何值,而不仅仅是其枚举数之一。

    标准 C++ 对此并没有真正的答案。在理想情况下,C++ 将有一个合约系统,您可以在其中预先声明 fun 要求参数 e 是枚举数之一。有了这些知识,GCC 就会知道交换机将采用所有控制路径。当然,即使 C++20 有合约(正在为 C++23 重新设计),仍然没有办法测试枚举值是否仅具有等于其枚举数之一的值。

    在不太理想的情况下,C++ 有办法明确告诉编译器一段代码预计无法访问,因此编译器可以忽略执行到达那里的可能性。不幸的是,该功能也没有使 C++20 成为现实。

    所以目前,您只能使用特定于编译器的替代方案。

    【讨论】:

      【解决方案2】:

      所有这三个编译器都有__builtin_unreachable() 扩展名。您可以使用它来抑制警告(即使返回值有构造函数问题)并引发更好的代码生成:

      enum class E{e1, e2};
      
      
      int fun(E e){
          switch(e){
              case E::e1: return 11;
              case E::e2: return 22;
          }
          __builtin_unreachable();
      
      }
      

      https://godbolt.org/z/0VP9af

      【讨论】:

      • 这非常有用。我什至可以把它放在结束卷曲之后以避免多余的线条。 ... } __builtin_unreachable();.
      • 不是我用MSVC而是参考MSVC没有__builtin_unreachable()godbolt.org/z/1FRDEK
      • @alfC 最好将它隐藏在宏后面而不是直接使用它,以防您可能想要支持的某些编译器没有它。我使用*(char*)0=0 作为替代__builtin_unreachable()。这无助于消除 msvc 上的警告,但您可以执行 UNREACHABLE; return 11 /*anything valid*/; 之类的操作,然后警告消失,UNREACHABLE 之后的额外 return 11; 不会使 gcc/clang/icc 上的代码更糟:godbolt.org/z/pbcnWC.
      • 你知道为什么(char*)0=0可以替代unreachable吗?
      • @alfC *(char*)0=someInt 永远无法执行或程序行为未定义。具有*(char*)0=someInt 的程序有效的唯一方法是该语句不可访问。所以*(char*)0=someInt应该和__builtin_unreachable()有同样的效果。实际上在 gcc/clang 上不需要,但这是不幸的,因为如果编译器确实这样对待它,那么就不需要 __builtin_unreachable() 扩展。
      【解决方案3】:

      这与enumswitch 无关,而是与编译器通过每条路径证明有效返回语句的能力有关。有些编译器在这方面比其他编译器做得更好。

      正确的做法是在函数的最后加上一个有效的return

      A fun(E e){
        switch(c){
          case E::e1: return A{11};
          ...
        }
        return A{11}; // can't get here, so return anything
      }
      

      编辑:如果您从无法访问的路径返回,某些编译器(如 MSVC)会抱怨。只需将编译器的#if 括起来即可。或者像我经常做的那样,只定义一个基于编译器定义的 RETURN(x)。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-04
      相关资源
      最近更新 更多