Skip to content
C++ Better Explained
Go back
C++ Type Casting Explained: static_cast, dynamic_cast, and More
Edit page

C++ Type Casting Explained: static_cast, dynamic_cast, and More

C++ is a strongly typed language — you can’t just mix an int and a double without telling the compiler what you mean. Type casting is how you convert a value from one type to another, explicitly and intentionally. C++ gives you four named cast operators that each have a clear job.


Why Named Casts? The Problem with Old-Style C Casts

In C, you cast like this:

double pi = 3.14;
int truncated = (int)pi;  // C-style cast

This works, but it’s dangerous. A C-style cast tries multiple conversions silently and can do things you didn’t intend — like stripping const or reinterpreting raw memory. C++ named casts make your intent explicit and are easier to search for in code reviews.


static_cast — The Everyday Cast

static_cast handles the most common conversions: numeric type changes and pointer casts along a known inheritance hierarchy.

#include <iostream>

int main() {
    double price = 9.99;
    int dollars = static_cast<int>(price);  // truncates to 9
    std::cout << "Dollars: " << dollars << "\n";

    int total = 7;
    int count = 2;
    // Without cast, this is integer division (3)
    double average = static_cast<double>(total) / count;
    std::cout << "Average: " << average << "\n";  // 3.5

    return 0;
}

The key point: static_cast is a compile-time check. The compiler verifies that the conversion makes sense, but it trusts you that it’s correct at runtime.


dynamic_cast — Safe Downcasting with Polymorphism

dynamic_cast is for pointer or reference conversions in class hierarchies. It performs a runtime check and returns nullptr if the cast fails (for pointers). You can only use it on classes that have at least one virtual function.

#include <iostream>

class Animal {
public:
    virtual void speak() { std::cout << "...\n"; }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override { std::cout << "Woof!\n"; }
    void fetch() { std::cout << "Fetching!\n"; }
};

class Cat : public Animal {
public:
    void speak() override { std::cout << "Meow!\n"; }
};

int main() {
    Animal* a = new Dog();

    // Try to downcast to Dog — succeeds
    Dog* d = dynamic_cast<Dog*>(a);
    if (d != nullptr) {
        d->fetch();  // Safe to call Dog-specific method
    }

    // Try to downcast to Cat — fails safely
    Cat* c = dynamic_cast<Cat*>(a);
    if (c == nullptr) {
        std::cout << "Not a Cat!\n";
    }

    delete a;
    return 0;
}

Use dynamic_cast when you genuinely don’t know the derived type at compile time. If you do know the type, prefer static_cast — it’s faster because it has no runtime overhead.

If you're looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you're a complete beginner or looking to solidify your understanding. Just $19.

const_cast — Adding or Removing const

const_cast is the only cast that can add or remove const from a type. You’ll rarely need it as a beginner, but it’s useful when you call a legacy API that doesn’t accept const but you know it won’t modify the data.

#include <iostream>

void printMessage(char* msg) {  // legacy API, no const
    std::cout << msg << "\n";
}

int main() {
    const char* greeting = "Hello, C++!";
    // Remove const to satisfy the legacy function signature
    printMessage(const_cast<char*>(greeting));
    return 0;
}

Warning: Never use const_cast to actually modify a const object. That causes undefined behaviour. Only use it to bridge API mismatches where you know no modification will occur.


reinterpret_cast — Raw Memory Reinterpretation

reinterpret_cast is the most dangerous cast. It treats the raw bits of one type as if they were a completely different type, with no conversion. This is mostly used in low-level systems programming — serial communication, binary file I/O, or interfacing with hardware registers.

#include <iostream>

int main() {
    int value = 65;
    // Reinterpret the int's address as a char pointer
    char* ch = reinterpret_cast<char*>(&value);
    std::cout << *ch << "\n";  // Prints 'A' (ASCII 65) on little-endian systems
    return 0;
}

As a beginner, you’ll rarely (if ever) need reinterpret_cast. When you see it in production code, it’s a signal to read carefully.


Quick Reference: Which Cast to Use?

SituationCast to Use
Convert int to double (or back)static_cast
Safe downcast in polymorphic hierarchydynamic_cast
Call a legacy non-const APIconst_cast
Low-level memory reinterpretationreinterpret_cast
Avoid all of the aboveRedesign if possible

The rule of thumb: always use the most restrictive cast that works. static_cast should be your default; reach for dynamic_cast when you need runtime safety, and treat const_cast and reinterpret_cast as last resorts.



Take Your C++ Further

If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.

👉 Get the C++ Better Explained Ebook — $19

📋

Free Download: The 10 Mistakes Every C++ Beginner Makes

A free 1-page checklist that shows the exact traps that slow down every C++ beginner — so you can avoid them from day one.

🔒 No spam. Unsubscribe anytime.


Edit page
Share this post on:

Previous Post
C++ Scope Resolution Operator (::) Explained for Beginners
Next Post
C++ Calculator Program: Build One Step by Step