Quick quiz! Given the following:



void f(unsigned int); void f(int); void f(char);

Which overload gets called by the following?

char x = 1; char y = 2; f(x + y);

Alternatives:

f(unsigned int) f(int) f(char) No-one knows the type of char + char

If you answered 4), congratulations! And if you answered 2), maybe you tried the code on your own computer? Most people will get f(int) when they try this code, but this is actually not specified by the standard. The only thing we know for sure is that it’s not 3), f(char) !

Let’s have a look at what’s going on:

Before being passed to operator + , the operands ( x and y ) go through a conversion. [expr.add]§8.7¶1:

The usual arithmetic conversions are performed for operands of arithmetic or enumeration type.

What are “the usual arithmetic conversions”?

[expr]§8¶11:

Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:

– [a bunch of rules for floats, enums etc]

– Otherwise, the integral promotions (7.6) shall be performed on both operands

So both char s go through integral promotions. Those are defined in [conv.prom]§7.6¶1:

A prvalue of an integer type other than bool , char16_t , char32_t , or wchar_t whose integer conversion rank (7.15) 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 .

So a char gets converted to an int if int can fit all possible values of a char . If not, they get converted to unsigned int . But any char should fit in an int , right? As it turns out, that’s not necessarily the case.

First, int could actually be the same size as char . [basic.fundamental]§6.9.1¶2:

There are five standard signed integer types : “signed char”, “short int”, “int”, “long int”, and “long long int”. In this list, each type provides at least as much storage as those preceding it in the list.

Note that it says “at least as much storage”, it doesn’t have to be more. So for instance you could have an sixteen bit system where both char and int are sixteen bits.

Second, char can be either signed or unsigned, it’s up to the implementation: [basic.fundamental]§6.9.1¶1:

It is implementation-defined whether a char object can hold negative values.

int is signed, so if char is also signed, all possible values of char will fit in an int . However, if char is unsigned, and int and char is the same size, char can actually hold larger values than int !

Let’s see an example. If char and int are both sixteen bits, int (which is always signed) can hold [-32768, 32767] . If char is signed, it can also hold [-32768, 32767] , and any char fits in an int . However, if char is unsigned, it can hold [0,65535] , half of which fall outside the range of int !

In the former case, char s get promoted to int s, but in the latter case, char s get promoted to unsigned int s before being summed.

So in practice, most systems will call f(int) , but some might call f(unsigned int) , and they would both be confirming to the standard.

If you enjoyed this post, you can subscribe to my blog, or follow me on Twitter.