C++ PROGRAMMING ยท 15 MIN STUDY

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.

C++17
#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 conversion

Brace 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_cast checks whether a value is within the destination type's range. An out-of-range floating-to-integer conversion has undefined behavior.
  • Using reinterpret_cast as 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

  1. What is the result of static_cast<double>(7) / 2?
  2. Does static_cast<int>(3.9) round to 4?
  3. Why can comparing a negative int with an unsigned integer be surprising?
Show reasoned answers
  1. 3.5, because the division uses floating-point arithmetic.
  2. No. It truncates toward zero, producing 3.
  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.

References