Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Range of and convertibility between different enum types

Under which conditions can one convert from one enumerated type to another?

Let's consider the following code:

#include <stdio.h>

int main(void) {
  enum enum_tag_1 { language } c = language;
  enum enum_tag_2 { lawyer = 256 } d = lawyer;
  d = c;
  c = 2;
  printf("%d\n", c);
  printf("%d\n", d);
}

We can now ask questions such as whether statements like c = 2; and d = c; are guaranteed to work.

The C17 standard says (draft, 6.7.2.2):

¶3 The identifiers in an enumerator list are declared as constants that have type int. [...]
¶4 Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined,[] but shall be capable of representing the values of all the members of the enumeration. [...]

That is, it seems that an enumerated type is simply a possibly implementation-defined integer type chosen by the implementation to be large enough for the given enumerator list, whose enumerators must all be of type int.

That is:

  • enum enum_tag_1 is a possibly implementation-defined type large enough to contain the int 0. This could be _Bool or a custom type whose range is {-1, 0, 1}. That is, the assignment c = 2; is not necessarily legal.
  • enum enum_tag_2 is a possibly implementation-defined type large enough to contain the int 256. This implies that it can at least hold the range [0, 1, ..., 511] (but not necessarily 512). Therefore the assignment d = language (value 0) is always legal. d = c; is legal in the above code, but assigning from enum enum_tag_1 to enum enum_tag_2 is not necessarily allowed for all values, since the former might be signed and the latter might be unsigned.
  • Even though the enumeration constants for enum enum_tag_1 and enum enum_tag_2 can only have values fitting into the type int, it is legal for the implementation to makes these types so big that one can assign very large numbers to them, like this:
    c = −9223372036854775808;
    d = 18446744073709551615;

This relies on the fact that integer types always have ranges of the form [0, 2N-1] (unsigned) or [-2N(+1), 2N-1] (signed), where N is the number of value bits (C17 standard, draft, 6.2.6.2 ¶1-2). (See this related question.)

But what about the general case? It seems that inter-convertibility of enums depends on the ranges of the types which the implementation chooses, making the answer implementation-dependent.

like image 298
Lover of Structure Avatar asked Oct 17 '25 01:10

Lover of Structure


1 Answers

NordicSemi has an opinion piece on this topic here: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.2-dev1/tfm/docs/technical_references/design_docs/enum_implicit_casting.html

... from which I quote:

According to the C99 standard 1:

§6.2.5, 16: An enumeration comprises a set of named integer constant values. Each distinct enumeration constitutes a different numerated type.

§6.7.2.2, 2: The expression that defines the value of an enumeration constant shall be an integer constant expression that has a value representable as an int.

§6.7.2.2, 3: The identifiers in an enumerator list are declared as constants that have type int and may appear wherever such are permitted.

§6.7.2.2, 4: Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration.

From these four quotes from the C99 standard 1, the following conclusions can be made:

  • an enumeration defines a new type and should be treated as such

  • the enumeration constants must only contains value representable as an int

  • the enumeration constants have type int

  • the actual type of the enumeration can be between char, signed and unsigned int. The compiler chooses the type it wants among those that can represent all declared constants of the enumeration.

The conclusion:

it is always safe to assign an enumeration constant to an int, but might be better to cast to show intent.

when casting an enumeration constant to another type, it should be checked that the constant can fit into the destination type.

when casting from an integer type (uint32_t, int32_t, etc) to an enumeration type, it should be checked that the integer’s value is one of the enumeration constants. The comparison needs to be done on the biggest type of the two so that no information is lost. C integer promotion should automatically do that for the programmer (check §6.3.1.8, 1 for the rules).

when casting from an enumeration type to an integer type, it should be checked that the enumeration type value fits into the integer type. The value of a variable which has the type of an enumeration type is not limited to the enumeration constants of the type. An enumeration constant will always fit into an int.

like image 130
Morten Jensen Avatar answered Oct 18 '25 15:10

Morten Jensen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!