Explicit casts and safe numeric conversions
Understand when C++ converts values automatically and how to make intentional conversions visible without hiding narrowing or precision loss.
Prerequisites and outcomes
Know basic numeric types, expressions, and initialization. By the end, you can distinguish implicit conversion from an explicit cast, select an appropriate cast for ordinary numeric conversion, and recognize conversions that lose information.
Why conversions happen
An expression may combine different arithmetic types. C++ applies conversion rules so the operation has compatible operands. These rules are useful, but a conversion can change a value or its meaning.
Implicit conversion example
int count = 5; double average = count; // 5 becomes 5.0
This conversion from integer to floating point is usually straightforward for small integers. The reverse direction can discard the fractional part:
double price = 19.95; int whole = price; // becomes 19; fractional part is discarded
Do not treat conversion to an integer as rounding. For a finite value whose truncated result is representable in the destination integer type, the fractional part is discarded (truncation toward zero). If the truncated result is outside the destination type's range, the behavior is undefined. This applies even when the destination type is unsigned; unsigned modulo arithmetic does not make an out-of-range floating-to-integer conversion safe.
Use static_cast for intentional conversions
int total = 17; int students = 4; double average = static_cast<double>(total) / students; // 4.25
Without the cast, integer division would produce 4 before assignment to double. The cast changes the type used by the division itself.
#include <iostream>
int main() {
int total = 17;
int students = 4;
double average = static_cast<double>(total) / students;
double reading = 23.8;
if (reading >= -100.0 && reading <= 100.0) {
int whole = static_cast<int>(reading);
std::cout << average << ' ' << whole << '\n';
}
}Expected output: 4.25 23. The cast happens before division, so the division is floating-point. The reading is checked against the application's accepted range before conversion to int; the conversion then truncates toward zero.
Check the value range before converting
static_cast makes the conversion explicit; it does not validate the value or prevent information loss. A conversion is safe only when the value is valid for the destination type and any loss is acceptable for the task. For a domain with a deliberately bounded range, validate that range before casting:
double reading = 23.8;
if (reading >= -100.0 && reading <= 100.0) {
int whole = static_cast<int>(reading); // 23
}This example is safe because the accepted interval fits even the minimum range guaranteed for int. The comparisons also reject infinities and NaN. For a different destination type or application, choose and check bounds that match that type and the application's requirements; do not copy these limits blindly.
Narrowing and brace initialization
double measurement = 3.7;
// int exact = measurement; // allowed, but loses fraction
// int checked{measurement}; // ill-formed: narrowing conversionBrace initialization rejects many narrowing conversions at compile time. This is a useful safety check, not a complete proof that every conversion preserves meaning.
Signed and unsigned values
int balance = -1; unsigned int count = 3; // Comparing balance and count can convert the signed value to unsigned.
Mixing signed and unsigned arithmetic can produce surprising results because the usual arithmetic conversions may convert a negative signed value to a large unsigned value. Prefer consistent types and heed compiler warnings.
Worked trace: percentage
int correct = 7; int total = 10; double percent = 100.0 * correct / total;
100.0 makes the arithmetic floating point, so the result is 70.0. If every operand were an integer, integer division could discard the fraction before assignment.
Common mistakes
- Casting only after an integer division has already happened.
- Assuming
static_castchecks whether a value is within the destination type's range. An out-of-range floating-to-integer conversion has undefined behavior. - Using
reinterpret_castas a general conversion tool; it is not. - Ignoring compiler warnings about signedness and narrowing.
Prerequisites: numeric types, arithmetic expressions, and implicit conversions. Review Numeric types and Expressions and arithmetic first.
Practice
- What is the result of
static_cast<double>(7) / 2? - Does
static_cast<int>(3.9)round to 4? - Why can comparing a negative
intwith an unsigned integer be surprising?
Show reasoned answers
- 3.5, because the division uses floating-point arithmetic.
- No. It truncates toward zero, producing 3.
- The usual arithmetic conversions can convert the negative value to unsigned, yielding a large value.
Summary and next step
Make meaningful conversions explicit, perform casts before the operation whose type matters, and use compiler warnings and brace initialization to expose risky code. Next, compare standard containers by their actual operation requirements.