We introduced type conversion in lesson 4.12 -- Introduction to type conversion and static_cast. To recap the most important points from that lesson:
- The process of converting a value from one type to another type 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 by using a cast operator, such as
static_cast
. - Conversions do not change the value or type being converted. Instead, a new value with the desired type is created as a result of the conversion.
In the first half of this chapter, we’re going to dig a bit deeper into how type conversion works. We’ll start with implicit conversions in this lesson, and explicit type conversions (casting) in upcoming lesson 10.6 -- Explicit type conversion (casting) and static_cast. Since type conversion is used all over the place, having some understanding of what’s happening under the hood when a conversion is needed is important.
Why conversions are needed
The value of an object is stored as a sequence of bits, and the data type of the object tells the compiler how to interpret those bits into meaningful values. Different data types may represent the “same” value differently. For example, the integer value 3
might be stored as binary 0000 0000 0000 0000 0000 0000 0000 0011
, whereas floating point value 3.0
might be stored as binary 0100 0000 0100 0000 0000 0000 0000 0000
.
So what happens when we do something like this?
float f{ 3 }; // initialize floating point variable with int 3
In such a case, the compiler can’t just copy the bits used to represent int
value 3
into the memory allocated for float
variable f
. If it were to do so, then when f
(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, the integer value 3
needs to be converted into the equivalent floating point value 3.0
, which can then be stored (using the bit representation for float
value 3.0
) in the memory allocated for f
.
Implicit type conversion
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 of the following cases:
When initializing (or assigning a value to) a variable with a value of a different data type:
double d{ 3 }; // int value 3 implicitly converted to type double
d = 6; // int value 6 implicitly converted to type double
When the type of a return value is different from the function’s declared return type:
float doSomething()
{
return 3.0; // double value 3.0 implicitly converted to type float
}
When using certain binary operators with operands of different types:
double division{ 4.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 doSomething(long l)
{
}
doSomething(3); // int value 3 implicitly converted to type long
So how does the compiler know how to convert int
value 3
to floating point value 3.0
anyway? We’ll talk about that in the rest of this lesson.
The standard conversions
As part of the core language, the C++ standard defines a collection of conversion rules known as the “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. For ease of understanding, these can be grouped into 5 general categories:
Category | Meaning | Link |
---|---|---|
Numeric promotions | Conversions of small integral types to int or unsigned int and float to double . |
10.2 -- Floating-point and integral promotion |
Numeric conversions | Other integral and floating point conversions that aren’t promotions. | 10.3 -- Numeric conversions |
Qualification conversions | Conversions that add or remove const or volatile . |
|
Value transformations | Conversions that change the value category of an expression | 12.2 -- Value categories (lvalues and rvalues) |
Pointer conversions | Conversions from std::nullptr to pointer types, or pointer types to other pointer types |
The most important ones to know are the numeric promotions and numeric conversions, which we will cover in upcoming lessons.
For advanced readers
Here is the full list of standard conversions:
Category | Standard Conversion | Description | Notes |
---|---|---|---|
Value transformation | Lvalue-to-rvalue | Converts lvalue to rvalue | 12.2 -- Value categories (lvalues and rvalues) |
Value transformation | Array-to-pointer | Converts C-style array to pointer to first array element (a.k.a. array decay) | 17.8 -- C-style array decay |
Value transformation | Function-to-pointer | Converts function to function pointer | 20.1 -- Function Pointers |
Value transformation | Temporary materialization | Converts value to temporary object | Not covered |
Qualification conversion | Qualification conversion | Adds or removes const or volatile from types |
Not covered |
Numeric promotions | Integral promotions | Converts smaller integral types to int or unsigned int |
10.2 -- Floating-point and integral promotion |
Numeric promotions | Floating point promotions | Converts float to double |
10.2 -- Floating-point and integral promotion |
Numeric conversions | Integral conversions | Integral conversions that aren’t integral promotions | 10.3 -- Numeric conversions |
Numeric conversions | Floating point conversions | Floating point conversions that aren’t floating point promotions | 10.3 -- Numeric conversions |
Numeric conversions | Integral-floating conversions | Converts integral and floating point types | 10.3 -- Numeric conversions |
Numeric conversions | Boolean conversions | Converts integral, unscoped enumeration, pointer, or pointer-to-memver to bool | 4.10 -- Introduction to if statements |
Pointer conversions | Pointer conversions | Converts std::nullptr to pointer, or pointer to void pointer or base class |
Not covered |
Pointer conversions | Pointer-to-member conversions | Converts std::nullptr to pointer-to-memberor pointer-to-member of base class to pointer-to-member of derived class |
Not covered |
Pointer conversions | Function pointer conversions | Converts pointer-to-noexcept-function to pointer-to-function | Not covered |
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. In other cases, statements may disallow certain types of conversions. For example:
int x { 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, such 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 lesson 11.3 -- Function overload resolution and ambiguous matches.
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. If finer detail is required for some uncommon case, the full rules are detailed in technical reference documentation for implicit conversions.
Let’s get to it!