Working With Data
Type Casting in C++
Table of Contents
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