Why is ‘char -> int’ promotion, but ‘char -> short’ is conversion (but not promotion)?

Historical motivation: C

The idea of integral promotions dates all the way back to pre-standard C. When providing arguments to variadic functions (...) or to functions without a prototype, promotions are applied. I.e. calling:

// function declaration with no prototype
void mystery();
// ...
char c="c";
mystery(c); // this promotes c to int

// in another .c file, someone could define
void mystery(int x) { /* ... */ }

Promotions are basically a “minimal” upgrade to the type, whereas conversions can be anything.
You could even argue that the design is a consequence of building on the B programming language, which didn’t even have multiple integer types like C does.

Relevant wording in the C++ Standard

Only the following are considered integer promotions:

A prvalue that is not a converted bit-field and has an integer type other than bool, char8_t, char16_t, char32_t, or wchar_t whose integer conversion rank is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.

[conv.prom] p2

The difference between promotions and conversions is explained here:

The conversions allowed as integral promotions are excluded from the set of integral conversions.

[conv.integral] p4

As you can see, there is some overlap between the two concepts, but any conversion which would also be a promotion is not considered a conversion.

Whether a standard conversion is a promotion or a conversion has an impact on overload resolution:

Standard conversion sequences are ordered by their ranks: an Exact Match is a better conversion than a Promotion, which is a better conversion than a Conversion. […]

[over.ics.rank] p4

Impact on overload resolution

As you’ve pointed out, char -> short is a conversion, and char -> int is a promotion.
This has the following impact:

// A conversion sequence from char -> int is empty
// because char -> int is a promotion, and so it doesn't contribute
// to the conversion sequence.
void f(int);
// A conversion sequence from char -> short has length 1
// because char -> short is not a promotion.
void f(short);

int main() {
    // Not an ambiguous call; calls f(int), because the conversion sequence
    // for this call is shorter.
    // Note that in C, a character literal has type 'int', not 'char', so
    // it is more symmetrical to favor 'int' whenever possible.

If C++ was designed from scratch nowadays, promotions and conversions would likely be defined a lot differently, however, the status quo is what we have, and it’s unlikely to change.
Changing this behavior and wording after all these years is basically impossible because of how much code depends on it.

As with so many design decisions in C++, the answer is: historical reasons.

Leave a Comment