#058 – How numeric promotions allow C++ to be portable and performant

Because C++ is designed to be portable and performant across a wide range of architectures, the language designers did not want to assume a given CPU would be able to efficiently manipulate values that were narrower than the natural data size for that CPU.

However, one consequence of this portability is that fundamental integer types do not always have the same size on every platform.

There are 3 different widths of an int

TypeCommon Size (Modern Systems)Guaranteed Minimum Size
short2 bytes (16 bits)16 bits
int4 bytes (32 bits)16 bits
long4 or 8 bytes*32 bits
long long8 bytes (64 bits)64 bits

The number of bits a data type uses is called its width. A wider data type is uses more bits, and a narrower data type uses less bits.

Consider the case where you wanted to write a function to print a value of type int:

#include <iostream>

void printInt(int x)
{
    std::cout << x << '\\n';
}

What happens if we want to call this function but the argument is a char, unsigned short, wchar_t, long long?

We’d have to make separate functions to be compatible with each alias! It would look like this:

void printChar(char x);
void printShort(short x);
void printBool(bool x);
void printUnsignedShort(unsigned short x);

Luckily we don’t thanks to numeric promotions. The argument automatically gets promoted to an int.

Introducing numeric promotions

This is the purpose of numeric promotions. This is when a type transforms from a narrow to a wider width.

C++ has 2 types of numeric promotions:

  1. integral promotions
  2. floating point promotions

Integral Promotions

Any type that has a smaller width than an int gets converted into an int

Original TypePromoted TypeNotes
boolintfalse becomes 0, true becomes 1
char / signed charintStandard character promotion
unsigned charintPromotes to int because int can hold 0–255
shortintStandard promotion
unsigned shortint or unsigned intBecomes unsigned int only if int is also 16-bit
enumintPromotes to its underlying integral type’s promotion

If the argument has a smaller width then the function parameter, numeric promotions takes place.

#include <iostream>

void printInt(int x) { std::cout << x << " is an int " << '\\n';}

int main() {

	short s { 3 }; // initialize short
	printInt(s); // short -> int
	printInt('d'); // char -> int
	printInt(false); // bool -> int
}

// Output
3 is an int 
100 is an int 
0 is an int 

Floating point promotions

This is when a float gets promoted to a double.

void acceptDouble(double x) {
	std::cout << x << '\\n';
}

int main() {
	acceptDouble(12.4f) // float -> double
}

NOTE: Numeric promotions are a subset of numeric conversions. A promotion is a widening conversion whilst a conversion can result in a truncation of information. E.g. doubleint.

Ultimately

Numeric promotions are one of the quiet mechanisms that make C++ both portable and efficient. They consist of integral and floating point promotions.

Integral promotions allow us to create functions that accept an int of a smaller width, saving us with overloading the function. The compiler does this:

This short is too small for the CPU to work with efficiently; turn it into an int before doing the math

Similarly, floating point promotions widen the width of the argument, making it compatible with the function.