is it correct to cast an integer to enumerated type? [duplicate]

戏子无情 提交于 2019-12-08 16:06:54

问题


My research has not yielded an answer for this yet (in SOF), but I am sure it must have been asked somewhere before, appologies if so.

I have created an enumerated type in c++ and then I read in a value from a message header and want to store it into a variable of my enum type, example:

// My defined enum type
enum myNumType_t
{
    MY_NUM_UNKNOWN = 0,
    MY_NUM1 = 1,
    MY_NUM2 = 2,
    MY_NUM3 = 3,
    MY_NUM4 = 4,
    MY_NUM5 = 5,
};

// In the code
int main()
{
    myNum_t myNum = MY_NUM_UNKNOWN;
    myNum = getMessageNumType(); // returns an int

    return 0;
}

So, this code does not compile in c++ because it can't convert the int to myNum_t, fair enough. So then if I cast it myNum = (myNum_t) getMessageNumType(); this of course now compiles. But does it do the right thing? What happens if the returned value is out of range of myNum_t? Is there a "best practise" here that I am missing?


回答1:


But does it do the right thing?

Assuming the value is valid for the enumeration type, yes.

What happens if the returned value is out of range of myNum_t?

It depends.

Up to (and excluding) the nearest power of two, you're guaranteed to get exactly that value. What your code then does with it is up to you. In other words, given your code, (int)(myNumType_t)7 is guaranteed to be equal to 7. This is important because some people use bitwise operations on enumeration values, so if an enumeration has BIT0 = 1, BIT1 = 2, BIT2 = 4, it is intended that BIT0 | BIT1 | BIT2 can be stored in that same enumeration type.

For larger values, not much of use can be said. Some implementations would store your enumeration in a single (8-bit) byte, so on those implementations, (int)(myNumType_t)257 could not possibly be equal to 257. On others, it might be. You're better off avoiding that by checking the integer value beforehand.




回答2:


The first question is whether you can trust the source of the value or not, and in general if you are reading messages off the network or any other input/output device, I would doubt that trust.

If you cannot trust it, you should be able to write a small function that tests the value received and maps it to the appropriate enumerator or fails if the value is out of range. Note that in the example code you provided, the compiler could choose any type for the enumeration that was able to represent all the values in the range 0-7, the standard does not make any additional guarantees.

While in practical terms the compiler will most probably pick a larger type like int for this particular case, the compiler can assume that the value is not outside of that range. If the value decoded from the message is larger than 8 you will be hitting undefined behavior and you might get results you don't really expect.

For a practical example, if the enumerators ranged from 0 to 3, both included, and you had this code:

enum_t value = (enum_t) getInt();
switch (value) {
case V0:
case V1:
case V2:
case V3:  std::cout << "valid\n"; break;
default:  std::cout << "unknown value\n"; break;
}

The compiler is allowed to remove the default case, as all values that can be represented in the enum_t are explicitly enumerated in the switch, so the default: case cannot be hit in a well formed program. The optimizer can change that test into:

enum_t value = (enum_t) getInt();
std::cout << "valid\n";

And you might end up being surprised as of why all values are valid even when your test case sends messages with invalid values!




回答3:


The only really safe int-to-enum cast is to cover ALL cases in the enum and in error-checking that the input might return and include boundary enums if there is a possibility of going out of range or returning an invalid value and then handle the bad value gracefully.

If the enum is guaranteed to be a contiguous range then it is a little simpler:

enum myNumType_t
{
    MY_NUM_UNKNOWN = 0,
    MY_NUM_MIN = 1,
    MY_NUM1 = 1,
    MY_NUM2 = 2,
    MY_NUM3 = 3,
    MY_NUM4 = 4,
    MY_NUM5 = 5,
    MY_NUM_MAX = MY_NUM5,
};

int main() {

    //...getMessage...

    myNumType_t result = static_cast<myNumType_t>(getMessageNumType());

    if(result < MY_NUM_MIN || result > MY_NUM_MAX) {
        //..handle out of range...
    }
}



回答4:


Yes, it is safe to cast from int type to enumeration type.

§ 4.5 Integral promotions

A prvalue of an unscoped enumeration type whose underlying type is fixed (7.2) can be converted to a prvalue of its underlying type. Moreover, if integral promotion can be applied to its underlying type, a prvalue of an unscoped enumeration type whose underlying type is fixed can also be converted to a prvalue of the promoted underlying type.

Just return enumeration type so you could avoid any cast troubles:

myNum_t getMessageNumType();



回答5:


You can use a cast to convert an integer value to an enumeration. If you do that, you're responsible for ensuring that the resulting value is meaningful. In the example code, you should make sure that getMessageNumType() returns a suitable value.

Values stored in an enumerated type are not restricted to the values named by the enumerators. For example, it's quite common to use an enumerator, along with appropriate overloaded operators, to implement a bit mask:

enum flag_values {
    flag1 = 0x01,
    flag2 = 0x02,
    flag3 = 0x04,
    flag4 = 0x08
};

flag_values flags = static_cast<flag_values>(flag1 | flag2);

That | operator should really be done with an overloaded operator; I left that out for simplicity. The cast is okay, but gets tedious to write everywhere.



来源:https://stackoverflow.com/questions/19159379/is-it-correct-to-cast-an-integer-to-enumerated-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!