The Life of a Programmer

Implicit Type Promotion and Conversion

C introduced it and C++ mastered it. The hellish world of implicit conversion and type promotion. A system which silently modifies, truncates, rounds, and otherwise mangles our variables.

(This article is part of the series on Defective C++)

C++ has inherited from C a series of implicit type conversions. On top of this it has added a class-based conversion mechanism. Perhaps this is quite convenient if you don’t care too much about types. Perhaps the concept is even sound, but something is lacking. As implemented they are very counter to the notion of a type-safe language.

For example, integer promotion has the vexing property that adding to short integers results in an normal integer. As does any numeric operation on a char. When trying to work with small values, or use bit-masks, this results in distracting casting to avoid compiler warnings. Warnings which you should not turn off if you care anything at all about static typing.

[sourcecode language=”cpp”]
uint8_t flags = 0;
uint8_t const flag_a = 0x12;

//this is the best syntax:
flags |= flag_a;
//but instead you have to do this to avoid warnings
flags = uint8_t( flags | flag_a );
[/sourcecode]

The craziest conversion involves the bool type. Any pointer implicitly converts to bool, as does a bool convert to any integer type. Explicit conversion to bool inside a conditional is a great convenience, but there is no reason to convert to/from bool by simply calling a function or assigning.

[sourcecode language=”cpp”]
void function( int value, bool flag ) { }

int main()
{
int a = 123;
bool flag = true;

//oops, a common mistake
function( flag, a );
}
[/sourcecode]

That the above compiles without warning (even with -Wall -Wconversion in gcc) is distressing!

A case of promotion gone wrong appears when you start using the extended integer types. Look at the following code and decide which function is called.

[sourcecode language=”cpp”]
#include <stdint.h>

void func( int64_t a ) { }
void func( double a ) { }

int main()
{
int a = 10;
func( a );
}
[/sourcecode]

Neither are called. This introduces an ambiguity since the int can be promoted to both an int64_t and a double. While promotion to double is a nice convenience, it should be clear that promotion to an integer is to be preferred.

Solutions

Type safety can only really be guaranteed if values cannot be lost by default. Therefore any conversion which truncates a value should never be done implicitly. That is, no casting to shorter or less precise types.

Class constructors should be marked as explicit by default, since the vast majority of single parameter constructors are not intended to be used as converters. I might even be willing to accept an argument that class conversion is never done implicitly and must always be explicitly noted. I might also accept an argument that says no implicit class conversion should be allowed at all.

Bool should be a distinct type, non-integral. In a way it should be treated a condition result, thus allowing all the familiar conversions and avoiding the broken ones.

Explicit casts to whatever you want should of course always be allowed. However it would be good to have clear functions which do this and indicate what will happen. Do you wish to truncate, round, assign to max on overflow, wrap-around, etc?

While all the implicit type conversion may seem to make programming easier, ultimately it just hides defects. These defects are often hard to find as well since they are not of a type that generate warnings or can be identified by run-time analysis tools. The rules for a statically typed language should be strict.

Please join me on Discord to discuss, or ping me on Mastadon.

Implicit Type Promotion and Conversion

A Harmony of People. Code That Runs the World. And the Individual Behind the Keyboard.

Mailing List

Signup to my mailing list to get notified of each article I publish.

Recent Posts