What Type Casting Is
Type casting means converting a value from one type to another - turning a double into an int, a char into its numeric code, or a base-class pointer into a derived one. C++ does some of these conversions for you automatically, but the ones it does silently are exactly where bugs hide.
There are two flavors: implicit conversions that happen on their own, and explicit casts that you write out. You already met one symptom of this in operators - integer division. Casting is how you take control of it.
Implicit Conversions
When you mix number types in an expression, C++ promotes the "smaller" type to the "larger" one so both sides match. This usually does what you want.
The trouble starts when the conversion goes the other way - from a wider type to a narrower one. That's a narrowing conversion, and it can lose data silently.
Float-to-int truncates toward zero - it does not round, so 3.99 becomes 3. And stuffing 300 into a char overflows. Many compilers warn here; some won't. When you genuinely mean to narrow, say so explicitly with a cast so the next reader knows it was on purpose.
Fixing the Integer-Division Trap
The single most common reason to cast is division. When both operands are integers, / does integer division and throws away the remainder.
The fix is static_cast<double> on one operand before the division. A frequent mistake is static_cast<double>(got / total) - that's too late, because got / total is already 0 by the time the cast runs, so you get 0.0. Cast an operand, not the result.
static_cast: Your Default Cast
C++ gives you four named casts. The one you'll use 95% of the time is static_cast<T>(value), which performs well-defined conversions between related types - numeric conversions, enum-to-int, void* back to a typed pointer, and up/down a class hierarchy when you already know the type.
Prefer static_cast over the old C-style cast (int)balance. A C-style cast will try any conversion to make the code compile - including the dangerous ones below - so it can silently strip const or reinterpret raw bytes. static_cast only allows conversions the compiler can actually justify, and the verbose static_cast<...> is trivial to search for in a code review.
// Avoid - C-style cast, no safety net:
int dollars = (int) balance;
// Prefer - explicit, checked, greppable:
int dollars = static_cast<int>(balance);
The Other Three Casts (Use Sparingly)
The remaining casts exist for specific, narrow jobs. Reach for them only when static_cast genuinely can't do it.
const_cast removes (or adds) const. Its only legitimate use is calling a C-style API that forgot to mark a parameter const. Modifying an object that was originally declared const through a const_cast is undefined behavior.
void legacyApi(char* msg); // old API, doesn't take const
const char* text = "hello";
legacyApi(const_cast<char*>(text)); // only OK if legacyApi doesn't write to it
reinterpret_cast reinterprets the raw bit pattern - e.g. a pointer as an integer address. It performs zero conversion and is wildly unsafe; almost always a sign you should rethink the design.
dynamic_cast safely converts a base-class pointer or reference down to a derived type at runtime, using the object's actual type. It requires a polymorphic base (a class with at least one virtual function) and returns nullptr if the cast doesn't apply.
If a had pointed at some other Animal, dynamic_cast<Dog*> would return nullptr and the else branch would run - which is exactly why it's safer than blindly using static_cast to go down a hierarchy.
Common Mistakes to Avoid
- Casting the result instead of an operand.
static_cast<double>(a / b)rounds away your fraction first. Castaorb. - Assuming float-to-int rounds. It truncates:
static_cast<int>(2.99)is2. For rounding usestd::round,std::lround, etc. - Reaching for a C-style cast. It hides which conversion happens. Use
static_castand you'll get a compile error when the conversion is unsafe instead of a silent surprise. - Narrowing into a too-small type. Casting
300tocharor a hugelongtointwraps or overflows. Pick a target type wide enough for the range.
Next: If-Else
Now that you can convert and compare values cleanly, the next step is making decisions with them. The if-else statement runs different code depending on whether a condition is true - the foundation of every branching program.
Frequently Asked Questions
What is the difference between static_cast and a C-style cast in C++?
A C-style cast like (int)x tries every conversion in turn - it can quietly become a dangerous reinterpret_cast or strip const. static_cast<int>(x) only performs related conversions the compiler can verify, so the compiler rejects nonsense. In modern C++, always prefer static_cast over C-style casts; it is safer and far easier to grep for.
How do I cast an int to a double in C++?
Use static_cast<double>(x). This matters most with division: 5 / 2 is integer division and gives 2, but static_cast<double>(5) / 2 gives 2.5. Cast one operand before the division happens - casting the result, static_cast<double>(5 / 2), is too late and still gives 2.0.
Why does casting a large value to a smaller type give a wrong number in C++?
Converting to a type that can't hold the value is a narrowing conversion. Float-to-int truncates the fraction (static_cast<int>(3.99) is 3), and an out-of-range integer either wraps (unsigned) or is implementation-defined (signed). The compiler usually won't stop you, so cast deliberately and make sure the target type is wide enough.