Type Conversion & Casting

How C++ turns one type's value into another, automatically or on your explicit command, and the named casts that make explicit conversions safe and readable.

Type conversion is the process of converting a value from one data type to another. C++ performs conversions automatically in many situations, but programmers can also explicitly request a conversion using casts. For example: ``cpp int a = 10; double b = a; ` Here, the int value 10 is automatically converted to double. Explicitly: `cpp double x = 10.75; int y = static_cast<int>(x); `` Here, the programmer explicitly requests conversion from double to int.

Why Type Conversion Is Needed

Different types are often used together in a program. For example:

The int value quantity needs to participate in an expression involving double. C++ applies its conversion rules to make the operation possible.

Type conversion is also useful when: converting integers to floating-point values, converting floating-point values to integers, performing arithmetic between different types, passing values to functions, returning values from functions, working with different character/integer types, and explicitly controlling the type used in an expression.

int quantity = 5;
double price = 19.99;
double total = quantity * price;

Two Main Types of Conversion

C++ conversions can broadly be divided into Implicit Conversion and Explicit Conversion (Casting).

// Implicit conversion: the compiler performs the conversion automatically.
int x = 10;
double y = x;

// Explicit conversion: the programmer explicitly requests the conversion.
double x2 = 10.75;
int y2 = static_cast<int>(x2);

Implicit Type Conversion

An implicit conversion occurs when C++ automatically converts one type into another according to its language rules.

int 10 is automatically converted to double 10.0. No explicit cast is required.

int x = 10;
double y = x;

Widening Conversion

A conversion from a type that can represent a narrower range or precision into a type that can generally represent a broader range is often called a widening conversion.

The integer value can be represented by the double (or int) in these examples.

These conversions generally do not lose the value for ordinary values within the destination's representable range. However, "widening" does not mean that every conversion is mathematically exact. Floating-point representation has its own precision rules.

int x = 100;
double y = x;

short x2 = 10;
int y2 = x2;

Narrowing Conversion

A conversion can lose information when converting to a type with a smaller range or precision.

The fractional part is discarded: 10.75 becomes 10.

Whether the value can be represented depends on the destination type's range.

double x = 10.75;
int y = x;
int x2 = 1000;
short y2 = x2;

Integer to Floating-Point Conversion

An integer can be converted to a floating-point type.

Conceptually: 25 becomes 25.0.

This is commonly used in arithmetic. Because one operand is double, the integer is converted for the operation.

int x = 25;
double y = x;
int total = 5;
double average = total / 2.0;

Floating-Point to Integer Conversion

A floating-point value can be converted to an integer.

Result: 25. The fractional portion is discarded.

The result is -25. The conversion truncates toward zero, it does not round.

This is not rounding. 25.9 becomes 25; 25.1 becomes 25; -25.9 becomes -25; -25.1 becomes -25. If rounding is required, use an appropriate mathematical operation rather than relying on integer conversion.

double x = 25.75;
int y = static_cast<int>(x);
double x2 = -25.75;
int y2 = static_cast<int>(x2);

Integer to Integer Conversion

C++ can convert between different integer types.

The reverse direction can potentially lose information:

If the destination type cannot represent the value, the result must be understood according to the specific conversion rules; do not assume that narrowing always behaves identically for every pair of types.

short a = 100;
int b = a;

int a2 = 100;
long long b2 = a2;
long long a3 = 100000;
short b3 = static_cast<short>(a3);

Signed and Unsigned Conversion

C++ allows conversion between signed and unsigned integer types.

For conversion to an unsigned integer type, the result is determined modulo 2^N, where N is the number of bits in the destination's unsigned type. For example, with a typical 32-bit unsigned type: -1 becomes 4,294,967,295. This can produce surprising comparisons and arithmetic.

The usual arithmetic conversions can cause a to be converted to an unsigned type before the comparison.

Best practice: be deliberate when mixing signed and unsigned integers.

int x = -10;
unsigned int y = x;
int a = -1;
unsigned int b = 1;
if (a < b)
{
    // May be surprising
}

Character Conversion

char is an integral type, so conversions between characters and integers are possible.

The resulting integer represents the character according to the implementation's character encoding.

The resulting character depends on the execution character set and whether the value is representable as a char.

Do not assume that every system uses a particular character encoding unless your program explicitly requires it.

char c = 'A';
int value = c;
int value2 = 65;
char c2 = static_cast<char>(value2);

Boolean Conversion

Values can be converted to bool.

Results: 0 becomes false; nonzero becomes true.

This is also why expressions can be used directly in conditions.

bool a = 0;
bool b = 10;
int count = 10;
if (count)
{
    // condition is true
}

Conversion During Arithmetic

C++ performs conversions when different arithmetic types are used together.

Conceptually: int a is converted for the operation to double, then double + double gives a double result. The exact rules are part of C++'s usual arithmetic conversions.

int a = 10;
double b = 2.5;
double result = a + b;

Integer Promotions

Before many arithmetic operations, smaller integer types can undergo integral promotion.

The operation does not generally take place as char + char. The operands are promoted, commonly to int, and the result is therefore commonly an int.

This is one reason expressions involving char or short can produce an int result.

char a = 10;
char b = 20;
auto result = a + b;
char + char
   │  integral promotion
   ↓
int + int
   │
   ↓
  int

Assignment Conversion

Conversions also occur when assigning one type to another.

The integer literal is converted to double.

The floating-point value is converted to int, resulting in 10. The compiler may warn about potentially lossy conversions depending on compiler settings.

double value = 10;
int value2 = 10.8;

Function Argument Conversion

Conversions can occur when passing arguments to functions.

The int argument is converted to double for the function call. This is one reason function parameter types are important: they influence which conversions are performed.

void process(double value)
{
    // ...
}

int x = 10;
process(x);

Return Value Conversion

A return statement can also involve conversion.

The integer value is converted to double.

The return value is converted to int, producing 10.

double getValue()
{
    return 10;
}
int getValue2()
{
    return 10.75;
}

Explicit Casting

Sometimes you want to explicitly control a conversion. Modern C++ provides four named casts: static_cast, dynamic_cast, const_cast, and reinterpret_cast. There is also C-style casting: (type)value. Modern C++ generally prefers the named casts because they make the programmer's intent clearer.

static_cast

static_cast is the most commonly used explicit conversion.

Syntax: static_cast<target_type>(expression)
double price = 19.99;
int value = static_cast<int>(price);

Integer Division and static_cast

One of the most important practical uses of casting is controlling arithmetic. Consider:

The result is 2.0, because a / b is performed as integer division first, and only then converted to double.

Now: 5 / 2 gives 2.5, because a is converted to double before the division happens.

Important difference: static_cast<double>(a / b) produces 2.0, while static_cast<double>(a) / b produces 2.5. The location of the cast matters.

int a = 5;
int b = 2;
double result = a / b;
double result2 = static_cast<double>(a) / b;

static_cast for Numeric Conversions

The cast makes the conversion explicit. However, explicit does not mean safe from information loss.

A cast does not magically make an out-of-range conversion valid.

int a = 100;
double b = static_cast<double>(a);

double x = 15.8;
int y = static_cast<int>(x);

long long value = 1000;
int smaller = static_cast<int>(value);
double x2 = 1e100;
int y2 = static_cast<int>(x2);

static_cast Is Not Bit Reinterpretation

static_cast<int>(someFloat) does not reinterpret the floating-point bits as an integer.

This performs a numeric conversion. It does not mean "same bits, different type interpretation."

Bit-level reinterpretation is a different operation and belongs to reinterpret_cast and other object-representation mechanisms.

double x = 10.5;
int y = static_cast<int>(x);

dynamic_cast

dynamic_cast is primarily used with polymorphic class hierarchies to perform checked conversions involving base and derived class types.

If the object actually refers to a Derived, the cast succeeds. If it does not, a pointer dynamic_cast produces nullptr. For references, failure results in std::bad_cast.

dynamic_cast is specifically related to runtime-checked class conversions and is separate from ordinary numeric conversion.

class Base
{
public:
    virtual ~Base() = default;
};

class Derived : public Base
{
};

Base* base = new Derived;
Derived* derived = dynamic_cast<Derived*>(base);

const_cast

const_cast can add or remove const or volatile qualification from a pointer or reference.

A const_cast can remove the qualification from the pointer type. However, this does not make an originally const object safely modifiable.

Removing const from a type is not permission to modify an object that was originally defined as const. const_cast should therefore be used only when the underlying object is actually modifiable and the conversion is required by an interface or similar circumstance.

const int value = 10;
const int* p = &value;
int* p2 = const_cast<int*>(p);
const int value2 = 10;
int* p3 = const_cast<int*>(&value2);
*p3 = 20;    // Undefined behavior

reinterpret_cast

reinterpret_cast is used for certain low-level conversions where the programmer explicitly requests a reinterpretation of a value or pointer.

This is valid when such a conversion is supported by the implementation. It does not perform an ordinary numeric conversion.

It should be used carefully because the resulting value may have strict requirements regarding validity, alignment, object lifetime, aliasing, and representation. It is generally not the normal choice for converting between arithmetic types.

std::uintptr_t address = reinterpret_cast<std::uintptr_t>(ptr);

C-Style Cast

C++ also supports the traditional C-style syntax:

However, modern C++ generally prefers:

Why? Because named casts make the intended operation much easier to identify. Compare int x = (int)value; with int x = static_cast<int>(value); The second form clearly communicates that an explicit compile-time conversion is intended.

int x = (int)3.14;
int x = static_cast<int>(3.14);

Cast Comparison

A quick comparison of the casts and their primary purpose:

General rule: for ordinary numeric conversions, static_cast<T>(value) is usually the appropriate choice.

Cast              Primary purpose
----------------- --------------------------------------------
static_cast       Ordinary compile-time conversions
dynamic_cast       Runtime-checked class hierarchy conversions
const_cast         Add/remove const or volatile qualification
reinterpret_cast   Low-level reinterpretation
C-style cast       Legacy/general cast syntax

Initialization and Narrowing Conversions

C++ list initialization provides additional protection against certain narrowing conversions. Consider:

This is allowed, although the value is converted to 3.

List initialization rejects this narrowing conversion. This makes brace initialization useful when you want the compiler to catch potentially lossy conversions.

int x = 3.14;
int x2{3.14};   // ill-formed: narrowing conversion rejected

Example: Comparing Different Conversion Styles

Both can produce the same result here. The explicit version communicates that the conversion is intentional.

int a = 10;
double b = 3.5;

// Implicit
double result1 = a + b;

// Explicit
double result2 = static_cast<double>(a) + b;

Conversion vs Casting

These terms are related but not identical.

A useful mental model is: conversion is the operation/rule; cast is the explicit syntax requesting a conversion.

// Conversion: the value changes from one type to another according to language rules.
int x = 10;
double y = x;

// Cast: syntax used to explicitly request or control a conversion/reinterpretation.
double x2 = 10.5;
int y2 = static_cast<int>(x2);

Literal Types and Conversion

Numeric literals have their own types. For example: 10 is normally an integer literal, 10.5 is normally a double literal, 10.5f is a float literal, and 10LL is a long long literal.

float x = 10.5;   // conversion from double to float
float x2 = 10.5f;   // uses a float literal directly, no conversion

Conversion in Boolean Contexts

Many expressions can be contextually converted to bool.

Pointers can also be tested in boolean contexts. The details of pointer conversions belong to the pointer topic, but the important idea here is that C++ supports contextual conversion to bool.

int count = 10;
if (count)
{
    // true
}

double value = 0.0;
if (value)
{
    // false
}
int* ptr = nullptr;
if (ptr)
{
    // Executes only when ptr is non-null
}

Best Practices

1. Prefer explicit casts when conversion is intentional, to make the intent obvious:

2. Prefer named C++ casts. Use static_cast<int>(value) instead of (int)value when writing modern C++.

3. Be careful with narrowing. If a conversion can lose information, make that possibility explicit and verify that the destination range is appropriate.

4. Avoid unnecessary casts. Do not write int x = static_cast<int>(10); when 10 is already an integer literal.

5. Be especially careful with signed/unsigned conversions. They can affect both arithmetic and comparisons.

6. Use brace initialization when narrowing should be rejected:

7. Don't confuse numeric conversion with bit reinterpretation. These are fundamentally different operations.

double average = static_cast<double>(total) / count;
int x{3.14};   // rejected

Quick Reference

// Implicit conversion
int a = 10;
double b = a;

// Explicit numeric conversion
double x = 10.75;
int y = static_cast<int>(x);

// Floating-point division
int p = 5;
int q = 2;
double r = static_cast<double>(p) / q;

// Boolean conversion
bool flag = 10;   // true
bool other = 0;   // false
Named casts:
static_cast<T>(value)
dynamic_cast<T>(value)
const_cast<T>(value)
reinterpret_cast<T>(value)

Mental Model

Think of C++ conversion as a change in the type used to represent or interpret a value according to defined language rules.

The key is to understand when the conversion happens, not just what type the final variable has.

What is the difference between an implicit conversion and an explicit conversion (cast)?

An implicit conversion happens automatically, according to the compiler's rules, without any special syntax (e.g. int x = 10; double y = x;). An explicit conversion (cast) is requested directly by the programmer using syntax such as static_cast<T>(value), making the intent visible in the code.

Given int a = 5; int b = 2; double result = a / b;, what value does result hold, and why?

result is 2.0. Because a and b are both int, a / b is evaluated as integer division first, producing 2, which is then converted to double when stored in result.

Why does static_cast<double>(a) / b give a different result than static_cast<double>(a / b) when a = 5 and b = 2?

static_cast<double>(a) / b converts a to double before the division happens, so the division is floating-point (5.0 / 2 = 2.5). static_cast<double>(a / b) performs the integer division first (5 / 2 = 2) and only then converts the integer result to double (2.0). The position of the cast changes when the conversion happens.

What happens when int x = -10; is assigned to unsigned int y = x;, and why can comparisons like (a < b) with a as int -1 and b as unsigned int 1 be surprising?

Converting a negative int to an unsigned type produces a value computed modulo 2^N (N = bits of the unsigned type), so -10 becomes a very large unsigned number. In the comparison, the usual arithmetic conversions convert the signed a to unsigned before comparing, so -1 becomes a huge positive number and the comparison does not behave the way it would for signed integers.

Why is int x{3.14}; ill-formed while int x = 3.14; compiles (with the value truncated to 3)?

int x = 3.14; uses copy initialization, which allows narrowing conversions (the double is silently converted to int, discarding the fraction). int x{3.14}; uses list (brace) initialization, which specifically rejects narrowing conversions, so the compiler flags it as an error instead of silently losing precision.

What does this print, and why?

It prints -25. static_cast<int> on a floating-point value truncates toward zero rather than rounding, so -25.75 becomes -25 (not -26).

What type does 'result' end up being, and why isn't it char?

result is of type int. Before the addition, both char operands undergo integral promotion to int (integer promotion), so the addition happens as int + int and produces an int, which auto then deduces as the type of result.

Write a function average(int total, int count) that returns the average as a double using proper floating-point division, avoiding integer-division truncation.

Casting one operand (total) to double before the division forces the division itself to be floating-point, so 7 / 2 evaluates to 3.5 instead of being computed as integer division (3) and only converted afterward.

Write a short program demonstrating the difference between const_cast misuse (undefined behavior) and a safe conversion using static_cast, by declaring a const int, attempting to modify it via const_cast, and separately converting a double to int via static_cast.

const_cast only removes the const qualifier from the pointer type; it does not make an object that was truly declared const safely writable, so writing through such a pointer is undefined behavior. static_cast<int>(price), in contrast, performs an ordinary, well-defined numeric conversion from double to int (here producing 19).

Original Value
      │
      ↓
Source Type
      │
      │ conversion
      ↓
Destination Type
      │
      ↓
Destination Value
5 / 2 → int / int → integer division → 2

static_cast<double>(5) / 2 → double / int → conversion → floating division → 2.5
double x = -25.75;
int y = static_cast<int>(x);
std::cout << y;
char a = 10;
char b = 20;
auto result = a + b;
std::cout << typeid(result).name();