我通常使用clang
来开发代码,并使用所有可能的合理警告(-Wall -Wextra [-Wpedantic]
)。此设置的好处之一是,编译器检查与所使用的枚举有关的switch
语句的一致性。例如下面的代码:
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
会抱怨(警告)if我忽略了e1
或e2
的情况,并且[[if没有默认情况。
<source>:4:12: warning: enumeration value 'e2' not handled in switch [-Wswitch]
switch(e){
此行为很好,因为它在编译时检查枚举和开关之间的一致性,从而使它们成为非常有用且不可分割的一对功能。
- 我不需要定义一个人工的
default
案例,对此我没有什么好要做的。- [它允许我省略一个没有好的东西返回的全局返回值(有时返回值不是像
int
这样的简单类型,例如,它可能是没有默认构造函数的类型。
enum class
,因此我只假设有效的情况,因为无效的情况只能由调用者端的讨厌转换产生。)现在是坏消息:
<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{} would be invalid
#endif
}
https://godbolt.org/z/3WC5v8
enum
或switch
无关,并且与编译器通过每条路径证明有效的return语句的能力有关。一些编译器在这方面比其他编译器要好。正确的方法是在函数的末尾添加
valid 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)。