is it correct to cast an integer to enumerated typ

2019-06-16 07:23发布

This question already has an answer here:

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?

5条回答
Root(大扎)
2楼-- · 2019-06-16 07:44

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();
查看更多
相关推荐>>
3楼-- · 2019-06-16 07:45

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楼-- · 2019-06-16 07:49

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.

查看更多
forever°为你锁心
5楼-- · 2019-06-16 07:54

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.

查看更多
我只想做你的唯一
6楼-- · 2019-06-16 07:55

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!

查看更多
登录 后发表回答