This is a quick post about the 4 explicit casts in modern C++ to replace the type cast in C++ (inherited from C).

static_cast Link to heading

This is used for basic type conversions and up-casting in class hierarchies (although implicit up-cast works).

// Basic type conversion
int i = 42;
double d = static_cast<double>(i);

// Pointer conversion in inheritance
class Base {};
class Derived : public Base {};

Derived* derived = new Derived();
Base* base = static_cast<Base*>(derived);  // Upcast - safe

Use static_cast when:

  • Converting between numeric types
  • Upcasting in inheritance hierarchies
  • Converting void* to typed pointers

const_cast Link to heading

Used to change (remove or add) const to a variable.

void modify_data(int* data) {
    *data = 100;
}

// Remove const to pass to function
const int value = 42;
modify_data(const_cast<int*>(&value));

// More common use case - removing const from this to use in const method
class MyClass {
    int cache_;
public:
    int expensive_calculation() const {
        // Remove const to modify mutable member
        const_cast<MyClass*>(this)->cache_ = 42;
        return cache_;
    }
};

Use const_cast when:

  • Interfacing with legacy APIs that don’t use const correctly
  • Implementing const and non-const versions of the same function
  • Working with mutable members in const functions

dynamic_cast Link to heading

Used for safe downcasting in inheritance hierarchies. Returns nullptr for pointers or throws std::bad_cast for references if the cast fails.

class Base {
public:
    virtual ~Base() {}  // Polymorphic base class
};

class Derived : public Base {};

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

if (derived) {
    std::cout << "Successful downcast\n";
} else {
    std::cout << "Failed downcast\n";
}

Use dynamic_cast when:

  • Performing runtime type checking
  • Safely downcasting in polymorphic hierarchies
  • You need to verify the actual type at runtime

reinterpret_cast Link to heading

The documentation says “Converts between types by reinterpreting the underlying bit pattern,” which I don’t fully understand, but these seem to be the use cases for it:

  • Converting between unrelated pointer types
  • Converting pointers to integers and vice versa
  • Low-level memory manipulation
  • Interfacing with C APIs or hardware
// Converting between pointer types
int* int_ptr = new int(42);
char* char_ptr = reinterpret_cast<char*>(int_ptr);

// Converting pointers to integers
uintptr_t address = reinterpret_cast<uintptr_t>(int_ptr);

// Converting function pointers
void (*func_ptr)() = reinterpret_cast<void(*)()>(address);