Implicit Type Conversion

We introduced type conversion in an earlier lesson. Let's recap the most important points:

  • The process of converting data from one type to another is called "type conversion"
  • Implicit type conversion is performed automatically by the compiler when one data type is required but a different data type is supplied
  • Explicit type conversion is requested using a cast operator, such as static_cast
  • Conversions don't change the data being converted. Instead, the conversion process uses that data as input and produces a converted result
  • When converting a value to another type, the conversion process produces a temporary object of the target type holding the result

In the first half of this chapter, we'll dig deeper into how type conversion works. We'll start with implicit conversions in this lesson, and explicit type conversions (casting) in an upcoming lesson. Since type conversion is used extensively, understanding what happens under the hood when a conversion is needed is important. This knowledge is also relevant for understanding how overloaded functions (functions that can have the same name as other functions) work.

Note
In this chapter, we'll focus on converting values to other value types. We'll cover other conversion types once we introduce prerequisite topics (such as pointers, references, inheritance, etc.).

Why Conversions Are Needed

An object's value is stored as a sequence of bits, and the object's data type tells the compiler how to interpret those bits into meaningful values. Different data types may represent the "same" value differently. For example, integer value 7 might be stored as binary 0000 0000 0000 0000 0000 0000 0000 0111, whereas floating point value 7.0 might be stored as binary 0100 0000 1110 0000 0000 0000 0000 0000.

So what happens when we do something like this?

float temperature{ 7 }; // initialize floating point variable with int 7

In such a case, the compiler can't just copy the bits used to represent int value 7 into the memory allocated for float variable temperature. If it did, then when temperature (which has type float) was evaluated, those bits would be interpreted as a float rather than an int, and who knows what float value we'd end up with!

Instead, integer value 7 needs to be converted into the equivalent floating point value 7.0, which can then be stored in the memory allocated for temperature (using the bit representation for float value 7.0).

When Implicit Type Conversion Happens

Implicit type conversion (also called automatic type conversion or coercion) is performed automatically by the compiler when an expression of some type is supplied in a context where some other type is expected. The vast majority of type conversions in C++ are implicit type conversions. For example, implicit type conversion happens in all these cases:

When initializing (or assigning a value to) a variable with a value of a different data type:

double roomTemp{ 72 }; // int value 72 implicitly converted to type double
roomTemp = 98; // int value 98 implicitly converted to type double

When the return value type differs from the function's declared return type:

float performCalculation()
{
    return 3.0; // double value 3.0 implicitly converted to type float
}

When using certain binary operators with operands of different types:

double result{ 8.0 / 3 }; // int value 3 implicitly converted to type double

When using a non-Boolean value in an if-statement:

if (5) // int value 5 implicitly converted to type bool
{
}

When an argument passed to a function is a different type than the function parameter:

void displayMessage(long messageId)
{
}

displayMessage(3); // int value 3 implicitly converted to type long

So how does the compiler know how to convert a value to a different type?

The Standard Conversions

As part of the core language, the C++ standard defines a collection of conversion rules known as "standard conversions". The standard conversions specify how various fundamental types (and certain compound types, including arrays, references, pointers, and enumerations) convert to other types within that same group.

As of C++23, there are 14 different standard conversions. These can be roughly grouped into 5 general categories:

Category Meaning
Numeric promotions Conversions of small integral types to int or unsigned int, and of float to double
Numeric conversions Other integral and floating point conversions that aren't promotions
Qualification conversions Conversions that add or remove const or volatile
Value transformations Conversions that change the value category of an expression
Pointer conversions Conversions from std::nullptr to pointer types, or pointer types to other pointer types

For example, converting an int value to a float value falls under the numeric conversions category, so the compiler simply needs to apply the int to float numeric conversion rules to perform such a conversion.

The numeric conversions and numeric promotions are the most important of these categories, and we'll cover them in more detail in upcoming lessons.

Type Conversion Can Fail

When a type conversion is invoked (whether implicitly or explicitly), the compiler will determine whether it can convert the value from the current type to the desired type. If a valid conversion can be found, then the compiler will produce a new value of the desired type.

If the compiler can't find an acceptable conversion, then the compilation will fail with a compile error. Type conversions can fail for any number of reasons. For example, the compiler might not know how to convert a value between the original type and the desired type.

For example:

int main()
{
    int value{ "42" };

    return 0;
}

Because there isn't a standard conversion from the string literal "42" to int, the compiler will produce an error. For example, GCC produces the error: error: invalid conversion from 'const char*' to 'int'.

In other cases, specific features may disallow some categories of conversions. For example:

int value{ 3.5 }; // brace-initialization disallows conversions that result in data loss

Even though the compiler knows how to convert a double value to an int value, narrowing conversions are disallowed when using brace-initialization.

There are also cases where the compiler may not be able to figure out which of several possible type conversions is the best one to use. We'll see examples of this in a later lesson on function overload resolution.

The full set of rules describing how type conversions work is both lengthy and complicated, and for the most part, type conversion "just works". In the next set of lessons, we'll cover the most important things you need to know about the standard conversions.

Let's get started!

Summary

Type conversion: The process of converting data from one type to another. Implicit type conversion is performed automatically by the compiler, while explicit type conversion uses cast operators.

Why conversions are needed: Different data types represent values using different bit patterns. Converting between types requires creating a new value with the appropriate bit representation for the target type.

When implicit conversion happens: Implicit conversions occur during initialization, function returns, binary operations with mixed types, conditionals, and function calls when argument types differ from parameter types.

Standard conversions: The C++ standard defines 14 standard conversions grouped into numeric promotions, numeric conversions, qualification conversions, value transformations, and pointer conversions.

Type conversion can fail: The compiler produces an error if it cannot find an acceptable conversion, such as when no conversion exists between types or when conversions are disallowed (like narrowing conversions in brace initialization).

Temporary objects: Type conversions produce temporary objects of the target type holding the converted value. The original value remains unchanged.

Understanding implicit type conversion is fundamental to C++ programming. While the compiler handles most conversions automatically, knowing when and how conversions occur helps you write correct code and understand compiler errors when conversions fail.