Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 8 of 8

C++ converts between numeric types automatically, and mostly that's helpful. Sometimes it silently throws away your data.

Widening: safe and silent

Converting to a type that can hold everything the original could is harmless:

#include <iostream>

int main() {
    int small = 42;
    double wide = small;
    long long huge = small;

    std::cout << wide << " " << huge << "\n";

    int a = 7;
    double b = 2.0;
    std::cout << (a / b) << "\n";
    return 0;
}
42 42
3.5

That last line shows usual arithmetic conversions at work: mixing int and double in one expression promotes the int, so you get 3.5 rather than 3.

Narrowing: lossy and also silent

The dangerous direction:

#include <iostream>

int main() {
    double precise = 3.99;
    int truncated = precise;

    std::cout << truncated << "\n";

    int big = 300;
    char small = big;
    std::cout << static_cast<int>(small) << "\n";

    double negative = -1.5;
    unsigned int wrapped = negative;
    std::cout << wrapped << "\n";
    return 0;
}
3
44
4294967295

Three separate disasters, all compiling without error:

  • 3.99 → 3. Note it truncates, it doesn't round. std::round() from <cmath> rounds.
  • 300 → 44. A char holds −128 to 127; 300 doesn't fit, and what's left is the low byte.
  • -1.5 → 4,294,967,295, via the unsigned wraparound from lesson 3.3.

None of these produce an error. With -Wall -Wextra you'll get warnings for some — another reason those flags matter.

Integer division: the classic

#include <iostream>

int main() {
    int total = 7;
    int count = 2;

    double wrong = total / count;
    double right = static_cast<double>(total) / count;

    std::cout << wrong << " " << right << "\n";
    return 0;
}
3 3.5

total / count is int / int, so the division itself is integer division producing 3. Assigning to a double afterwards is too late — the fraction is already gone. You must convert before dividing.

This is the single most common numeric bug in beginner C++.

Brace initialization stops it

C++11 introduced {} initialization, and its best feature is that narrowing conversions inside braces are diagnosed:

double precise = 3.99;
int truncated{precise};    // narrowing — diagnosed

warning: narrowing conversion of 'precise' from 'double' to 'int' [-Wnarrowing]

The standard says this is ill-formed, but compilers differ on how hard they push back: Clang rejects it outright, while GCC issues a warning by default for backward compatibility. You can make GCC agree with the standard by adding -Werror=narrowing, which is worth doing.

Either way, the point stands: the same code written with = says nothing at all. Braces turn a silent data loss into something the compiler mentions. That's the entire argument for preferring them:

#include <iostream>

int main() {
    int a{42};
    double b{3.14};
    char c{'x'};
    bool d{true};

    int safe{static_cast<int>(3.99)};   // explicit: I meant to truncate

    std::cout << a << " " << b << " " << c << " " << d << " " << safe << "\n";
    return 0;
}
42 3.14 x 1 3

You can still narrow — you just have to say so with a cast, which makes it visible to the next reader. The conversion isn't blocked; the silence is.

Braces also solve the uninitialized-variable problem cleanly, since int x{}; zero-initializes:

#include <iostream>

int main() {
    int a{};
    double b{};
    bool c{};

    std::cout << a << " " << b << " " << c << "\n";
    return 0;
}
0 0 0

static_cast: converting on purpose

When you genuinely want a conversion, say so:

#include <iostream>

int main() {
    double value = 9.7;

    std::cout << static_cast<int>(value) << "\n";
    std::cout << static_cast<char>(66) << "\n";
    std::cout << static_cast<double>(7) / 2 << "\n";
    return 0;
}
9
B
3.5

static_cast<T>(x) is deliberately verbose. C also allows (int)value, which works but is easy to miss when scanning code and is harder to search for. C++ has three other cast forms — dynamic_cast, const_cast, and reinterpret_cast — covered later; static_cast is the one you'll use most.

Practical rules

  1. Initialize with braces — int count{0}; — so narrowing is diagnosed (add -Werror=narrowing on GCC to make it fatal).
  2. Convert before dividing, not after.
  3. Use static_cast when you mean it, so the intent is on the page.
  4. Keep -Wall -Wextra on to catch the narrowing that happens outside braces.

Try it

Write a program that computes the average of int values 7, 8, and 10, correctly, as a double. Then show what happens when the conversion is done in the wrong place.

Module 3 complete

You now know what the fundamental types are and how big they are, when to reach for fixed-width integers, how overflow behaves differently for signed and unsigned, why floating point can't represent 0.1, that char is really a number, when auto helps, what const and constexpr promise, and how brace initialization stops silent data loss.

That's the value layer. Next up: Module 4 — Control Flow, where programs start making decisions.

Questions about this lesson

Log in to ask a question.