给定enum class val { foo = 1, bar = 2, baz = 4 };
可以定义:
val operator|(val x, val y)
{
return static_cast<val>(static_cast<int>(x) | static_cast<int>(y));
}
但是,这样做在语义上是否正确?
我倾向于 否 ,如下所示,看似表现良好的例子:
int convert(val x)
{
switch(x)
{
case val::foo: return 42;
case val::bar: return 53;
case val::baz: return 64;
}
}
使用g ++和clang ++编译时,调用convert(val::foo | val::bar)
将返回0
。
我的问题是双重的:
1.a上述链接示例中哪个编译器正确,g ++或clang ++?
我可以想到几种可能的实现:
enum class val { foo, bar, baz, size };
using val_flags = std::set<val>; // (1)
using val_flags = std::vector<bool>; // (2)
using val_flags = std::bitset<val::size>; // (3)
using val_flags = std::underlying_type<val>::type; // (4)
更新
谢谢大家的回答。我最终复活了我的旧枚举运算符模板。如果有人感兴趣,可以在此处找到:github.com
答案 0 :(得分:6)
以下,看似表现良好的例子:
不是,但只做一个小改动:
int convert(val x)
{
switch(x)
{
case val::foo: return 42;
case val::bar: return 53;
case val::baz: return 64;
}
return 9; // ADDED THIS LINE
}
一切都会好的。另一种解决方法是使用default:
案例并返回那里。
您的现有代码通过到达非void
返回类型的函数的右括号来触发未定义的行为 1 。因为它是未定义的行为,所以两个编译器都是正确的。
保持enum
类型的值的语义是枚举值的按位或组合,这些语义是明确定义和保证的。该标准要求enum
的实例可以存储任何整数值,而不会使用比定义的任何枚举器值更多的位,其中包括所有按位或组合。用来说这个的形式语言有点混乱,但在这里(注意你的案例是enum class
,这些总是有固定的基础类型,第一句适用):
对于其基础类型是固定的枚举,枚举的值是的值 基础类型。否则,对于枚举,其中e min 是最小的枚举数,e max 是 最大值,枚举值是b min 到b max 范围内的值,定义如下:设K 对于二进制补码表示为1,对于一个补码或符号幅度表示为0。 b max 是大于或等于max的最小值(| e min | - K,| e min |)并且等于2 M - 1,其中 M是非负整数。如果e min 是非负的,则b min 为零,否则为 - (b max + K)。的大小 如果b min 是最大的,足以保存枚举类型的所有值的最小位字段是max(M,1) 否则为0和M + 1。可以定义具有未由其任何枚举器定义的值的枚举。如果枚举器列表为空,则枚举的值就像枚举具有值为0的单个枚举器一样。
(来自n4582,第7.2节[dcl.enum]
)
1 来自6.6.3 [stmt.return]
:
在构造函数,析构函数或具有 cv
void
返回类型的函数的末尾流动,相当于没有操作数的返回。否则,流出除main(3.6.1)以外的函数的末尾会导致未定义的行为。
答案 1 :(得分:3)
将值存储在枚举器未表示的枚举中是否在语义上正确?非常欢迎该标准的摘录。
如果值在枚举范围内,则为是。 Ben Voigt提供了该标准的摘录。我更喜欢查看cppreference,因为我发现它更具可读性(尽管它没有相同的权威值)。
整数,浮点和其他枚举类型的值可以通过static_cast转换为任何枚举类型。如果转换为枚举的基础类型的值超出此枚举的范围,则结果未指定(直到C ++ 17)未定义的行为(因为C ++ 17)。如果基础类型是固定的,则范围是基础类型的范围。如果基础类型不固定,则范围是最小位字段可能的所有值,其大小足以容纳目标枚举的所有枚举器。请注意,此类转换后的值可能不一定等于为该类型定义的任何命名枚举数。
上述链接示例中的哪个编译器是正确的,g ++或clang ++?
问题是你的代码调用了未定义的行为,但出于与枚举无关的原因,正如评论和Ben Voigt所指出的那样。所以两个编译器都是正确的。
请注意,您并不需要convert
函数来试验这些行为。
enum class val { foo = 1, bar = 2, baz = 4 };
val operator|(val x, val y) {
return static_cast<val>(static_cast<int>(x) | static_cast<int>(y));
}
int main() {
std::cout << static_cast<int>(val::foo | val::bar); // prints 3
}
是否有标准(或建议)的方式来表示C ++中的标志?
我会在范围的结构(或类)中寻找静态constexpr变量。
struct Flags {
static constexpr unsigned int foo = 0x01;
static constexpr unsigned int bar = 0x02;
static constexpr unsigned int baz = 0x04;
};
答案 2 :(得分:2)
除了其他答案(已经指出问题)之外,如果您需要一组标志,请考虑std::bitset
,并最终使用enum
为这些位赋予名称(不是他们的重量),如
enum flags_names { f0, f1, f2, f3, NFlags }
typedef std::bitset<NFlags> flags;
现在你可以做到
flags fs;
fs[f0] = true; fs[f2] = false;
if(fs[f0]) ...
etc.