Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Manual Type Casting Techniques
Convert types explicitly with static_cast and understand why C-style casts are dangerous.
Explicit Type Conversion: Casting and static_cast
In our lesson on implicit type conversion, we learned that the compiler automatically converts values between types when needed. This works well when promoting values to wider types.
Many C++ beginners attempt something like:
double result = 20 / 8; // performs integer division, assigns 2.0 to result
Since 20 and 8 are both int, integer division occurs, evaluating to int value 2. This value then converts to double value 2.0 for assignment. Probably not the intended outcome.
For literals, replacing one or both with double literals enables floating-point division:
double result = 20.0 / 8.0; // performs floating-point division, assigns 2.5 to result
But what about variables?
int numerator { 20 };
int denominator { 8 };
double result = numerator / denominator; // integer division, assigns 2.0
Integer division happens here, giving result the value 2.0. Literal suffixes don't work with variables. We need to convert one (or both) operands to floating-point type for floating-point division.
C++ provides several type casting operators (commonly called casts) for explicit type conversion by the programmer. Since programmers explicitly request these casts, this is explicit type conversion (versus implicit type conversion where the compiler converts automatically).
Type Casting
C++ supports 5 cast types: static_cast, dynamic_cast, const_cast, reinterpret_cast, and C-style casts. The first four are named casts.
Advanced note: Cast types overview:
| Cast | Description | Safe? |
|---|---|---|
| static_cast | Compile-time conversions between related types | Yes |
| dynamic_cast | Runtime conversions on pointers/references in polymorphic hierarchies | Yes |
| const_cast | Adds or removes const | Only for adding const |
| reinterpret_cast | Reinterprets bit-level representation as another type | No |
| C-style casts | Combination of static_cast, const_cast, or reinterpret_cast | No |
Each cast takes an expression (evaluating to a value or object) and a target type as input. Output is the converted result.
We'll cover C-style casts and static_cast in this lesson (the most commonly used casts).
We discuss dynamic_cast after covering prerequisite topics.
const_cast and reinterpret_cast should generally be avoided as they're rarely useful and potentially harmful.
Avoid `const_cast` and `reinterpret_cast` unless absolutely necessary.
C-style Cast
In standard C programming, casting uses operator() with the target type inside parentheses and the value immediately after. In C++, this is a C-style cast. You may encounter these in C-converted code.
Example:
#include <iostream>
int main()
{
int numerator { 20 };
int denominator { 8 };
std::cout << (double)numerator / denominator << '\n'; // C-style cast to double
return 0;
}
We use a C-style cast to convert numerator to double. Since the left operand of operator/ now evaluates to floating-point, the right operand also converts to floating-point, enabling floating-point division.
C++ offers an alternative function-style cast resembling a function call:
std::cout << double(numerator) / denominator << '\n'; // function-style cast to double
The function-style cast clarifies what's being converted (resembling a function argument).
C-style casts have significant problems in modern C++.
First, though a C-style cast appears singular, it can perform various conversions depending on usage, including static cast, const cast, or reinterpret cast (the latter two should be avoided). C-style casts don't clarify which cast(s) actually occur, making code harder to understand and opening doors to inadvertent misuse (thinking you're doing a simple cast but actually doing something dangerous). This often produces runtime-only errors.
Second, since C-style casts are just a type name, parenthesis, and variable/value, they're difficult to identify (making code harder to read) and even harder to search for.
Conversely, named casts are easy to spot and search for, make their purpose clear, have limited abilities, and produce compilation errors on misuse.
Avoid C-style casts.
Advanced note: C-style casts attempt these C++ casts in order:
const_caststatic_caststatic_castfollowed byconst_castreinterpret_castreinterpret_castfollowed byconst_cast
One unique C-style cast capability: converting derived objects to inaccessible base classes (e.g., from private inheritance).
static_cast for Most Value Conversions
By far the most used C++ cast is static cast, accessed via the static_cast keyword. static_cast explicitly converts values from one type to another.
You've seen static_cast convert char to int so std::cout prints it as an integer instead of character:
#include <iostream>
int main()
{
char letter { 'z' };
std::cout << static_cast<int>(letter) << '\n'; // prints 122 instead of z
return 0;
}
To perform a static cast, we use the static_cast keyword, then place the target type in angled brackets. Inside parentheses goes the expression whose value converts. The syntax resembles a function call to static_cast<type>() with the expression as an argument. Static casting returns a temporary object direct-initialized with the converted value.
Here's how static_cast solves our introductory problem:
#include <iostream>
int main()
{
int numerator { 20 };
int denominator { 8 };
std::cout << static_cast<double>(numerator) / denominator << '\n'; // prints 2.5
return 0;
}
static_cast<double>(numerator) returns a temporary double object containing the converted value 20.0. This temporary serves as the left operand for floating-point division.
static_cast has two important properties.
First, static_cast provides compile-time type checking. If we attempt an impossible conversion, we get a compilation error.
int value { static_cast<int>("Hello") }; // invalid: produces compilation error
Second, static_cast is (intentionally) less powerful than C-style casts, preventing certain dangerous conversions (like those requiring reinterpretation or discarding const).
Favor `static_cast` when converting between types.
Advanced note: Since static_cast uses direct initialization, any explicit constructors of the target class are considered when initializing the returned temporary object.
Using static_cast for Explicit Narrowing Conversions
Compilers often warn about potentially unsafe (narrowing) implicit type conversions. Consider:
int value { 65 };
char ch = value; // implicit narrowing conversion
Casting int (2 or 4 bytes) to char (1 byte) is potentially unsafe (the compiler can't determine if the integer overflows the char range), so compilers typically warn. With list initialization, the compiler would error.
To bypass this, use static cast to explicitly convert integer to char:
int value { 65 };
char ch { static_cast<char>(value) }; // explicit conversion from int to char
When we do this, we explicitly tell the compiler this conversion is intended, accepting responsibility for consequences (e.g., char overflow if it happens). Since this static cast output is type char, initializing variable ch creates no type mismatch, thus no warnings or errors.
Another example where compilers typically complain about double to int data loss:
int total { 100 };
total = total / 2.5;
To explicitly indicate intention:
int total { 100 };
total = static_cast<int>(total / 2.5);
We discuss more static_cast uses with class types in later lessons.
Casting vs Initializing a Temporary Object
To convert variable x to int, two conventional approaches exist:
static_cast<int>(x), returning a temporaryintobject direct-initialized withx.int { x }, creating a temporaryintobject direct-list-initialized withx.
Avoid int ( x ), which is a C-style cast. This returns a temporary int direct-initialized with x's value (like expected from syntax), but has other C-style cast downsides (like potentially performing dangerous conversions).
Three notable differences exist between static_cast and the direct-list-initialized temporary:
int { x }uses list initialization, disallowing narrowing conversions. Great for variable initialization where we rarely want data loss. But when casting, we presumably know what we're doing, and narrowing conversions should be allowed if desired. The narrowing restriction can impede this.
Example showing platform-specific issues:
#include <iostream>
int main()
{
int numerator { 20 };
int denominator { 8 };
std::cout << double{numerator} / denominator << '\n'; // OK if int is 32-bit, narrowing if 64-bit
}
We decided to convert numerator to double for floating-point division instead of integer division. On 32-bit architecture, this works (a double can represent all 32-bit int values, so it isn't narrowing). But on 64-bit architecture, this is narrowing. Since list initialization disallows narrowing conversions, this won't compile on 64-bit architectures.
-
static_castmakes conversion intent clearer. Though more verbose than the direct-list-initialized alternative, that's beneficial here, making conversions easier to spot and search for, ultimately making code safer and more understandable. -
Direct-list-initialization of a temporary only allows single-word type names. Due to a syntax quirk, several C++ places allow only single-word type names (the standard calls these "simple type specifiers"). While
int { x }is valid conversion syntax,unsigned int { x }is not.
See this example producing a compile error:
#include <iostream>
int main()
{
unsigned char letter { 'a' };
std::cout << unsigned int { letter } << '\n';
return 0;
}
Simple workarounds exist, easiest being a single-word type alias:
#include <iostream>
int main()
{
unsigned char letter { 'a' };
using UInt = unsigned int;
std::cout << UInt { letter } << '\n';
return 0;
}
But why bother when you can just static_cast?
For these reasons, we generally prefer static_cast over direct-list-initialization of a temporary.
Prefer `static_cast` over initializing a temporary object when conversion is desired.
Summary
Explicit type conversion (casting): Programmer-requested type conversion using cast operators, as opposed to implicit conversion performed automatically by the compiler.
C-style casts: Legacy casting syntax (type)value or type(value) that should be avoided. Can perform dangerous conversions, hard to search for, and unclear about which conversion is performed.
static_cast: The primary C++ cast for explicit type conversions. Provides compile-time type checking, is easy to search for, prevents dangerous conversions, and clearly indicates programmer intent.
Named casts: C++ cast operators (static_cast, dynamic_cast, const_cast, reinterpret_cast) that explicitly state their purpose and are type-safe.
Narrowing conversion safety: Use static_cast to explicitly mark intentional narrowing conversions, silencing compiler warnings and documenting that data loss is expected.
Temporary object initialization: While int{x} can convert x to int, static_cast<int>(x) is preferred for conversions because it's clearer, works with multi-word types, and doesn't disallow narrowing.
Understanding explicit type conversion and preferring static_cast helps you write clearer, safer code that makes conversion intent obvious and prevents accidental dangerous conversions.
Quiz
Question #1
What's the difference between implicit and explicit type conversion?
Show Solution
Implicit type conversion happens automatically whenever one data type is expected but a different one is supplied. Explicit type conversion happens when the programmer uses a cast to explicitly request conversion from one type to another.
Manual Type Casting Techniques - Quiz
Test your understanding of the lesson.
Practice Exercises
Explicit Type Conversion with static_cast
Practice using static_cast for safe, explicit type conversions. Learn when and why to use explicit casts instead of implicit conversions.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!