Is enum { a } e = 1; valid?

Viewed 1657

A simple question: is enum { a } e = 1; valid?

In other words: does assigning a value, which isn't present in the set of values of enumeration constants, lead to well-defined behavior?

Demo:

$ gcc t0.c -std=c11 -pedantic -Wall -Wextra -c
<nothing>

$ clang t0.c -std=c11 -pedantic -Wall -Wextra -c
<nothing>

$ icc t0.c -std=c11 -pedantic -Wall -Wextra -c
t0.c(1): warning #188: enumerated type mixed with another type
# note: the same warning for enum { a } e = 0;

$ cl t0.c /std:c11 /Za /c
<nothing>
4 Answers

From the C18 standard in 6.7.2.2:

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.

So yes enum { a } e = 1; is valid. e is a 'integer' type so it can take the value 1. The fact that 1 is not present as an enumeration value is no issue. The enumeration members only give handy identifiers for some of possible values.

I can find no reference in this Draft C11 Standard (in terms of "constraints") that prohibits an assignment to a enum type such as yours (and the C18 Standard quoted in this other answer uses essentially identical wording).

However, that C11 Draft does provide this, in Annexe I – Common Warnings:

1     An implementation may generate warnings in many situations, none of which are specified as part of this International Standard. The following are a few of the more common situations.

2

   —    A value is given to an object of an enumerated type other than by assignment of an enumeration constant that is a member of that type, or an enumeration object that has the same type, or the value of a function that returns the same enumerated type (6.7.2.2).

But that suggested warning could equally well apply to an assignment like enum { a } e = 0;, where the value of the RHS corresponds to a valid enumeration constant but it is actually neither an enumeration constant of the type nor an object of that enumerated type.

Valid in C

This is valid in C as per e.g. the 202x working draft:

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

particularly due to the "compatible type" requirement:

6.2.7/1 Two types have compatible type if their types are the same. Additional rules [...]


... implementation-defined and possible unspecified/undefined behavior in C++14 and earlier/C++17 and later

Whilst out of scope for this Q&A (C tag, not C++), it may be interesting to point out that the same does not hold for C++, where "C style" unscoped enums with no fixed underlying type have subtle differences from C. For details about the C++ case, see the following Q&A:

In short

The validity of enum { a } e = 1; is ensured for the particular value of 1, a combination of different reasons, based on the definition of enum types, conversion rules of compatible types, and compatibility guarantees.

Spoiler alert: it's much more complex in C18 than the quote of any single paragraph (see my comment under the accepted answer).

Full explanation

I'll quote the C18 standard step by step (N2176 to be precise). Highlights are from me.

First, we know that enum { a } is an integer type:

6.2.5. Types
16: An enumeration comprises a set of named integer constant values. Each distinct enumeration constitutes a different enumerated type.
17: The type char, the signed and unsigned integer types, and the enumerated types are collectively called integer types.

Then, we can demonstrate that if enum { a } is an integer type "large enough" to hold 1, the assignment would valid (because there is no explicit statement of the contrary):

6.2.7 Compatible types and composite types
1:Two types have compatible type if their types are the same. (...)

6.3. Conversions
2: Unless explicitly stated otherwise, conversion of an operand value to a compatible type causes no change to the value or the representation.

6.7.2.2 Enumeration specifiers
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.

But is the type large enough? From here we deduct that the value of a is 0. Since it's also the largest enum constant in the set, we can deduct that enum { a } is "at least" compatible with char:

6.7.2.2 Enumeration specifiers
3: (...) An enumerator with = defines its enumeration constant as the value of the constant expression. If the first enumerator has no =, the value of its enumeration constant is 0. Each subsequent enumerator with no = defines its enumeration constant as the value of the constant expression obtained by adding 1 to the value of the previous enumeration constant.

In conclusion, the assignment is legal, because the litteral 1 is also compatible with char.

Beware of implementation dependent assumptions!

But be careful, such an integer assignment is not necessarily valid for whatever integer you choose:

enum { a } = INT_MAX;  // Implementation dependent, see J.3.9 
enum { a, b=INT_MAX } = 1024;  // ok: b requires int as compatible type  

In the first case, the enum is only guaranteed to be compatible with char. But we have no guarantee that it's compatible with an int:

J.3. Implementation-defined behavior
(...) **J.3.9. Structures, unions, enumerations, and bit-fields
(...)

  • The integer type compatible with each enumerated type (6.7.2.2).

In the second case, we used specification of an inteer value to force a compatible type with int.

Related