Working With Data

Type Casting in C++


Introduction to Type Casting

Type casting (or type conversion) changes a value from one type to another. C++ provides four cast operators for explicit conversions.

Cast Operators Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                         C++ CAST OPERATORS                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌─────────────────┐    ┌─────────────────┐                                 │
│  │  static_cast    │    │  dynamic_cast   │                                 │
│  │                 │    │                 │                                 │
│  │ • Compile-time  │    │ • Runtime       │                                 │
│  │ • No RTTI       │    │ • Uses RTTI     │                                 │
│  │ • Fast          │    │ • Polymorphic   │                                 │
│  │ • Most common   │    │ • Safe          │                                 │
│  └─────────────────┘    └─────────────────┘                                 │
│                                                                              │
│  ┌─────────────────┐    ┌─────────────────┐                                 │
│  │  const_cast     │    │ reinterpret_cast│                                 │
│  │                 │    │                 │                                 │
│  │ • const/volatile│    │ • Bit pattern   │                                 │
│  │ • Add or remove │    │ • Dangerous     │                                 │
│  │ • Use carefully │    │ • Low-level     │                                 │
│  │ • API compat    │    │ • Non-portable  │                                 │
│  └─────────────────┘    └─────────────────┘                                 │
│                                                                              │
│  Use hierarchy:                                                              │
│  static_cast > dynamic_cast > const_cast > reinterpret_cast                 │
│  (most preferred)                         (least preferred)                 │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

C-Style Casts

Traditional C-style casts are powerful but dangerous.

// C-style cast syntax
int x = (int)3.14;        // Explicit cast
int y = int(3.14);        // Function-style cast

// What C-style casts actually do:
double d = 3.14;
int i = (int)d;           // Tries: static_cast, reinterpret_cast, const_cast
                          // in that order until one works

⚠️ Problems with C-Style Casts

┌─────────────────────────────────────────────────────────────────────────────┐
│                    WHY AVOID C-STYLE CASTS                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  1. INVISIBLE INTENT                                                         │
│     ┌────────────────────────────────────────────────────────────────────┐  │
│     │  (int)ptr    // Is this intentional pointer-to-int cast?           │  │
│     │              // Or is it a mistake?                                │  │
│     │              // Hard to search for in codebase                     │  │
│     └────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│  2. TOO POWERFUL - Does multiple things silently                            │
│     ┌────────────────────────────────────────────────────────────────────┐  │
│     │  const int* cp = &x;                                               │  │
│     │  int* p = (int*)cp;     // Removes const AND casts type           │  │
│     │                          // Would need const_cast in C++           │  │
│     └────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
│  3. NO COMPILE-TIME CHECKS                                                   │
│     ┌────────────────────────────────────────────────────────────────────┐  │
│     │  class Base {};                                                    │  │
│     │  class Derived : public Base {};                                   │  │
│     │  class Unrelated {};                                               │  │
│     │                                                                    │  │
│     │  Base* b = new Derived();                                          │  │
│     │  Unrelated* u = (Unrelated*)b;  // Compiles! Undefined behavior!  │  │
│     └────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

static_cast

The most commonly used cast. Performs compile-time type checking.

Use Cases

// 1. Numeric conversions
double d = 3.14159;
int i = static_cast<int>(d);           // 3 (truncates)

float f = static_cast<float>(10) / 3;  // 3.333... (avoids integer division)

// 2. Pointer conversions in class hierarchy
class Base {};
class Derived : public Base {};

Derived* d = new Derived();
Base* b = static_cast<Base*>(d);       // Upcast (safe, implicit anyway)
Derived* d2 = static_cast<Derived*>(b); // Downcast (unsafe, no runtime check!)

// 3. void* to typed pointer
void* vp = malloc(sizeof(int));
int* ip = static_cast<int*>(vp);       // Convert void* to int*

// 4. Enum to int and vice versa
enum class Color { Red, Green, Blue };
int colorValue = static_cast<int>(Color::Green);  // 1
Color c = static_cast<Color>(2);                  // Color::Blue

// 5. Explicit constructor calls
class MyClass {
public:
    explicit MyClass(int x) {}
};
MyClass obj = static_cast<MyClass>(42);  // Calls constructor

static_cast Visualization

┌─────────────────────────────────────────────────────────────────────────────┐
│                        static_cast USE CASES                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ✅ SAFE CONVERSIONS:                                                        │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  int → double        (widening)                                        │ │
│  │  float → int         (narrowing, may lose precision)                   │ │
│  │  Derived* → Base*    (upcast)                                          │ │
│  │  enum class → int    (underlying type)                                 │ │
│  │  void* → T*          (when you know the actual type)                   │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ⚠️ POTENTIALLY UNSAFE (but compiles):                                       │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  Base* → Derived*    (downcast - no runtime check!)                    │ │
│  │                      Use dynamic_cast for safety                       │ │
│  │  double → int        (loses fractional part)                           │ │
│  │  int → short         (may overflow)                                    │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ❌ WILL NOT COMPILE:                                                        │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  int* → double*      (unrelated pointer types)                         │ │
│  │  const int* → int*   (removing const - use const_cast)                 │ │
│  │  int → void*         (use reinterpret_cast)                            │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

dynamic_cast

Runtime type-safe casting for polymorphic types (classes with virtual functions).

How It Works

class Base {
public:
    virtual ~Base() = default;  // Must have virtual function for dynamic_cast
};

class Derived : public Base {
public:
    void derivedMethod() { cout << "Derived method" << endl; }
};

class AnotherDerived : public Base {};

// Downcast with runtime check
Base* b = new Derived();

Derived* d = dynamic_cast<Derived*>(b);
if (d != nullptr) {
    d->derivedMethod();  // Safe!
} else {
    cout << "Cast failed" << endl;
}

// Reference version throws on failure
try {
    Derived& dRef = dynamic_cast<Derived&>(*b);
    dRef.derivedMethod();
} catch (const std::bad_cast& e) {
    cout << "Cast failed: " << e.what() << endl;
}

dynamic_cast Behavior

┌─────────────────────────────────────────────────────────────────────────────┐
│                        dynamic_cast BEHAVIOR                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Given:                                                                      │
│  ┌─────────────┐                                                            │
│  │    Base     │  ◄─── Has virtual function                                │
│  └──────┬──────┘                                                            │
│         │                                                                   │
│    ┌────┴────┐                                                              │
│    │         │                                                              │
│  ┌─┴──┐   ┌──┴─┐                                                            │
│  │ D1 │   │ D2 │                                                            │
│  └────┘   └────┘                                                            │
│                                                                              │
│  Base* b = new D1();                                                        │
│                                                                              │
│  POINTER CASTS:                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  dynamic_cast<D1*>(b)  →  Valid D1* pointer   ✓                        │ │
│  │  dynamic_cast<D2*>(b)  →  nullptr             ✗ (wrong type)           │ │
│  │  dynamic_cast<Base*>(b) →  Valid Base* pointer ✓ (upcast)              │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  REFERENCE CASTS:                                                            │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  dynamic_cast<D1&>(*b)  →  Valid D1& reference ✓                       │ │
│  │  dynamic_cast<D2&>(*b)  →  throws std::bad_cast ✗                      │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  REQUIREMENTS:                                                               │
│  • Source type must be polymorphic (have virtual functions)                  │
│  • Uses RTTI (Runtime Type Information)                                      │
│  • Has runtime overhead                                                      │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Cross-Cast (Sibling Cast)

class Base { virtual ~Base() = default; };
class D1 : public Base {};
class D2 : public Base {};
class Multi : public D1, public D2 {};

Multi* m = new Multi();
D1* d1 = m;

// Cross-cast from D1 to D2 (through Multi)
D2* d2 = dynamic_cast<D2*>(d1);  // Works! Finds Multi, then D2

const_cast

Adds or removes const or volatile qualifiers.

Use Cases

// 1. Remove const (use carefully!)
const int x = 42;
int* p = const_cast<int*>(&x);
// *p = 100;  // UNDEFINED BEHAVIOR if x was truly const!

// 2. Safe use: calling non-const API with const data
void legacyAPI(char* str);  // Old API doesn't take const

void modernFunction(const char* str) {
    // We know legacyAPI won't modify str
    legacyAPI(const_cast<char*>(str));
}

// 3. In const member function, modify mutable-like members
class Cache {
    mutable int cachedValue;
    bool cacheValid;

public:
    int getValue() const {
        if (!cacheValid) {
            // Need to modify cache in const function
            Cache* self = const_cast<Cache*>(this);
            self->cachedValue = computeValue();
            self->cacheValid = true;
        }
        return cachedValue;
    }
};

// 4. Avoid code duplication (const/non-const overloads)
class Container {
    int data[100];
public:
    int& at(int i) {
        // Non-const version delegates to const version
        return const_cast<int&>(
            static_cast<const Container*>(this)->at(i)
        );
    }

    const int& at(int i) const {
        // Actual implementation
        return data[i];
    }
};

const_cast Rules

┌─────────────────────────────────────────────────────────────────────────────┐
│                        const_cast RULES                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ✅ VALID USES:                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  const T*  ↔  T*        (add/remove const from pointer)                │ │
│  │  const T&  →  T&        (remove const from reference)                  │ │
│  │  volatile T*  ↔  T*     (add/remove volatile)                          │ │
│  │  const volatile T* ↔ T* (add/remove both)                              │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ⚠️ UNDEFINED BEHAVIOR:                                                      │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  const int x = 42;                                                     │ │
│  │  int* p = const_cast<int*>(&x);                                        │ │
│  │  *p = 100;  // UB! x was declared const                               │ │
│  │                                                                        │ │
│  │  // Only safe if original object was NOT const:                        │ │
│  │  int y = 42;                                                           │ │
│  │  const int* cp = &y;                                                   │ │
│  │  int* p2 = const_cast<int*>(cp);                                       │ │
│  │  *p2 = 100;  // OK! y was not originally const                        │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ❌ CANNOT DO:                                                               │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  const_cast<int>(3.14)     // Not for value conversion                 │ │
│  │  const_cast<double*>(ip)   // Not for type conversion                  │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

reinterpret_cast

Low-level bit reinterpretation. Treats memory as a different type.

Use Cases

// 1. Pointer to integer and back
int* p = new int(42);
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
cout << "Address: " << hex << addr << endl;
int* p2 = reinterpret_cast<int*>(addr);

// 2. Between pointer types (dangerous!)
struct NetworkPacket {
    uint32_t header;
    uint8_t data[100];
};

char buffer[104];
receiveData(buffer);
NetworkPacket* packet = reinterpret_cast<NetworkPacket*>(buffer);

// 3. Function pointer casts
using FuncPtr = void(*)();
void* libHandle = dlopen("lib.so", RTLD_NOW);
void* sym = dlsym(libHandle, "myFunction");
FuncPtr func = reinterpret_cast<FuncPtr>(sym);
func();

// 4. Type punning (better to use memcpy or std::bit_cast)
float f = 3.14f;
// Old way (UB according to strict aliasing):
// uint32_t bits = *reinterpret_cast<uint32_t*>(&f);

// Better (C++20):
// uint32_t bits = std::bit_cast<uint32_t>(f);

reinterpret_cast Dangers

┌─────────────────────────────────────────────────────────────────────────────┐
│                    reinterpret_cast DANGERS                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ⚠️ STRICT ALIASING VIOLATION:                                               │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  float f = 1.0f;                                                       │ │
│  │  int* pi = reinterpret_cast<int*>(&f);                                 │ │
│  │  int i = *pi;  // UNDEFINED BEHAVIOR!                                  │ │
│  │                // Compiler may optimize incorrectly                     │ │
│  │                                                                        │ │
│  │  // Safe alternatives:                                                  │ │
│  │  int i;                                                                │ │
│  │  memcpy(&i, &f, sizeof(i));                // C++11                    │ │
│  │  // OR                                                                  │ │
│  │  int i = std::bit_cast<int>(f);            // C++20                    │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ⚠️ ALIGNMENT ISSUES:                                                        │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  char buffer[5];                                                       │ │
│  │  int* p = reinterpret_cast<int*>(buffer + 1);                          │ │
│  │  *p = 42;  // May crash on architectures requiring aligned access!    │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  ⚠️ POINTER SIZE MISMATCH:                                                   │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │  void* p = ...;                                                        │ │
│  │  int addr = reinterpret_cast<int>(p);  // Truncates on 64-bit!        │ │
│  │  // Use uintptr_t instead of int                                       │ │
│  └────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│  WHEN TO USE:                                                                │
│  • Low-level I/O, hardware access                                           │
│  • Serialization/deserialization                                            │
│  • Interfacing with C libraries                                             │
│  • NEVER for normal application code                                        │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Comparison & Recommended Conventions

Cast Selection Flowchart

┌─────────────────────────────────────────────────────────────────────────────┐
│                     WHICH CAST SHOULD I USE?                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Start: What are you trying to do?                                          │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │  Numeric conversion (int↔double↔float)?                              │   │
│  │     → static_cast                                                     │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │  Upcast in class hierarchy (Derived* → Base*)?                       │   │
│  │     → Implicit cast (no cast needed) OR static_cast                  │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │  Downcast in class hierarchy (Base* → Derived*)?                     │   │
│  │     → Is the class polymorphic (has virtual functions)?              │   │
│  │        YES → dynamic_cast (runtime check, safe)                      │   │
│  │        NO  → static_cast (no check, you must be sure!)               │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │  Add or remove const/volatile?                                       │   │
│  │     → const_cast                                                      │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │  Convert between unrelated pointer types?                            │   │
│  │  Convert pointer to integer or vice versa?                           │   │
│  │  Low-level bit manipulation?                                         │   │
│  │     → reinterpret_cast (last resort!)                                │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │  Type punning (view bits of one type as another)?                    │   │
│  │     → std::bit_cast (C++20) or memcpy                                │   │
│  │     → NOT reinterpret_cast (strict aliasing violation)               │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Cheat Sheet Table

Cast Compile/Runtime Safe Use Case
static_cast Compile Mostly Numeric, void*, upcast, explicit downcast
dynamic_cast Runtime Yes Safe polymorphic downcast
const_cast Compile Careful Add/remove const/volatile
reinterpret_cast Compile No Low-level bit manipulation
C-style Compile No AVOID

Recommended Conventions

// ✅ DO:
// 1. Use static_cast for numeric conversions
double d = 3.14;
int i = static_cast<int>(d);

// 2. Use dynamic_cast for safe downcasting
if (Derived* d = dynamic_cast<Derived*>(basePtr)) {
    d->derivedMethod();
}

// 3. Use const_cast only when necessary
legacyFunction(const_cast<char*>(constString));

// 4. Minimize use of reinterpret_cast
// Only for hardware/serialization needs

// ❌ DON'T:
// 1. Don't use C-style casts
int* p = (int*)voidPtr;  // Bad
int* p = static_cast<int*>(voidPtr);  // Good

// 2. Don't downcast without checking
Derived* d = static_cast<Derived*>(basePtr);  // Risky
// Use dynamic_cast or be 100% sure of the type

// 3. Don't remove const from truly const objects
const int x = 42;
int* p = const_cast<int*>(&x);
*p = 100;  // UNDEFINED BEHAVIOR!

Cheat Sheet

// static_cast - compile-time, most common
static_cast<NewType>(expression)

// dynamic_cast - runtime, polymorphic types
dynamic_cast<NewType*>(pointer)     // Returns nullptr on failure
dynamic_cast<NewType&>(reference)   // Throws std::bad_cast on failure

// const_cast - add/remove const or volatile
const_cast<NonConstType*>(constPointer)

// reinterpret_cast - bit reinterpretation
reinterpret_cast<DestType>(expression)

// C++20 bit_cast - type-safe bit reinterpretation
std::bit_cast<DestType>(source)

Compile & Run

g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples

# Enable RTTI (needed for dynamic_cast)
g++ -std=c++17 -frtti examples.cpp -o examples

# Disable RTTI (dynamic_cast won't work)
g++ -std=c++17 -fno-rtti examples.cpp -o examples