Issue 1021: Integer promotions for enumerations

Authors: Joseph Myers
Date: 2025-12-18
Submitted against: C23
Status: Open

The following issue is based on a discussion that started in reflector message 34564.

When an expression with enumerated type (with or without fixed underlying type) is used in an expression, if the type's integer conversion rank (which equals the rank of the underlying type) is less than or equal to the rank of int and unsigned int, the integer promotions convert it to int or unsigned int (depending on whether int can represent all values of the type). When an expression with enumerated type with larger rank is used in an expression, it is unchanged by the integer promotions, but the usual arithmetic conversions still act to convert it to the underlying type.

This means that in contexts that apply the integer promotions but not the usual arithmetic conversions, such as unary + and -, shift operators, and the default argument promotions when passing arguments to variadic functions, such an expression is not converted to the underlying type.

For unary + and -, this differs from some implementation practice, which accepts the following example that is not valid according to the current wording.

enum e : long { E1 };
enum f : long { F1 };
extern typeof (+(enum e)0) x;
extern typeof (+(enum f)0) x; // Not valid; enum e and enum f not compatible.

For shift operators, the standard wording assumes that the promoted left operand has a signed or unsigned integer type. But while enumerated types have signed or unsigned behavior, they do not fall into the definition of signed or unsigned types.

For variadic functions, the following example shows that calling va_arg with a different enumerated type whose underlying type differs in signedness can be valid when calling it with a different enumerated type with the same underlying type is not valid, which does not seem intuitive.

#include <stdarg.h>

typedef enum e : long { E1 } e;
typedef enum f : long { F1 } f;
typedef enum g : unsigned long { G1 } g;

void foo(int, ...)
{
  va_list ap;
  va_start(ap);
  va_arg(ap, g);  // okay
  va_arg(ap, f);  // UB
}

int main(void)
{
  foo(0, (e)0, (e)0);
}

No specific wording change is proposed here to address these issues. The committee should first give direction on whether the conversion to the underlying type should be added to the integer promotions (and removed from the usual arithmetic conversions) for enumerated types with rank larger than that of int and unsigned int. If not, then appropriate fixes should be considered individually for each of the cases listed here.