在 clang、gcc 和 icc 中始终处理 switch 枚举类返回

alf*_*lfC 5 c++ enums switch-statement c++11 enum-class

我通常使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

这种行为很好,因为

  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
Run Code Online (Sandbox Code Playgroud)

我为这项工作找到的唯一解决方案是有一个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
    }
}
Run Code Online (Sandbox Code Playgroud)

这很糟糕,因为我上面提到的原因(甚至没有涉及返回类型没有默认构造函数的情况)。但这也很糟糕,因为我可以再次忘记枚举案例之一,现在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
    }
}
Run Code Online (Sandbox Code Playgroud)

或者

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

有一个更好的方法吗?

这是示例: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
}
Run Code Online (Sandbox Code Playgroud)

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

Nic*_*las 5

重要的是要注意,鉴于您对 的初始定义fun,执行以下操作在C++ 中完全合法的:

fun(static_cast<E>(2));
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

在一个不太理想的世界中,C++ 将有一种方法可以明确地告诉编译器一段代码是不可访问的,因此编译器可以忽略执行到达那里的可能性。不幸的是,该功能也没有使 C++20 成为可能。

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


PSk*_*cik 3

所有这三个编译器都有__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();

}
Run Code Online (Sandbox Code Playgroud)

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