Question

When to Use static_cast, dynamic_cast, const_cast, and reinterpret_cast in C++

cpppointerscastingc++-faq

Question

In C++, what are the correct use cases for these casting forms?

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • (type)value (C-style cast)
  • type(value) (function-style cast)

How do you decide which cast to use in specific situations, and what are the differences in safety and intent between them?

Short Answer

By the end of this page, you will understand what each C++ cast is designed for, when it is appropriate to use it, and which casts are risky or should usually be avoided. You will also learn how to choose the safest cast for common programming situations, especially when converting between numeric types, working with inheritance, removing const, or dealing with low-level memory operations.

Concept

C++ provides several different cast operators because not all type conversions mean the same thing.

In older C-style code, a cast like (type)value could do many different things at once. That makes code shorter, but also less clear and less safe. C++ introduced named casts so the programmer's intent is explicit.

Here is the big idea:

  • static_cast is for normal, compile-time conversions.
  • dynamic_cast is for safe casting in inheritance hierarchies at runtime.
  • const_cast is for adding or removing const or volatile qualifiers.
  • reinterpret_cast is for low-level bit-pattern reinterpretation.
  • C-style and function-style casts are less explicit and may perform one of several kinds of conversions.

Why this matters:

  • It makes code easier to read.
  • It makes unsafe operations easier to spot.
  • It helps the compiler catch mistakes.
  • It communicates whether a conversion is routine, runtime-checked, qualifier-related, or low-level.

A useful rule of thumb is:

  1. Avoid casting if you can. Prefer designing code so conversion is unnecessary.
  2. If you must cast, use the most specific named cast.
  3. Avoid C-style casts in modern C++ because they can hide dangerous conversions.

static_cast

Use static_cast when the conversion is well-defined and checked at compile time.

Common uses:

  • numeric conversions like double to int
  • converting void* to a typed pointer when you know the real type
  • upcasting in inheritance (Derived* to Base*)
  • downcasting only when you are certain the object is really that derived type
  • calling explicit constructors or conversion operators

Example:

double price = 19.99;
int whole = static_cast<int>(price); // whole becomes 19

This is clear and intentional: you are converting a double to an int, which may lose the fractional part.

dynamic_cast

Use dynamic_cast when working with polymorphic classes and you need to safely move down an inheritance hierarchy.

It performs a runtime check.

  • For pointer casts: failed cast returns nullptr
  • For reference casts: failed cast throws std::bad_cast

This only works correctly when the base class is polymorphic, usually meaning it has at least one virtual function.

Example:

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

class Dog : public Animal {
public:
    void bark() {}
};

Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal);

if (dog) {
    dog->bark();
}

This is safer than static_cast because it checks the actual runtime type.

const_cast

Use const_cast only to add or remove const or volatile.

Example:

void printMessage(char* text) {
    std::cout << text << '\n';
}

const char* msg = "Hello";
printMessage(const_cast<char*>(msg));

However, removing const is dangerous if you then modify an object that was originally declared const. That leads to undefined behavior.

Safe-ish use cases are rare, such as calling old APIs that should accept const but do not.

reinterpret_cast

Use reinterpret_cast for very low-level operations where you want to treat a value's bits or address as a different type.

Example:

std::uintptr_t raw = 0x1000;
int* ptr = reinterpret_cast<int*>(raw);

This does not convert the value in a normal semantic sense. It tells the compiler to interpret it as another type. This is highly platform-dependent and often unsafe.

Typical uses include:

  • low-level systems programming
  • hardware access
  • serialization internals
  • interfacing with certain C APIs

Most application code should avoid it.

C-style cast: (type)value

This older syntax can perform multiple kinds of cast, including behavior similar to:

  • static_cast
  • const_cast
  • reinterpret_cast
  • combinations of them

That is exactly why it is discouraged in modern C++.

Example:

int x = 65;
char c = (char)x;

The problem is not that it never works. The problem is that it hides what kind of conversion you intended.

Function-style cast: type(value)

This is often similar to a C-style cast, though it can also look like a constructor call.

Example:

int count = int(3.8); // count becomes 3

This syntax is still less explicit than named casts when the goal is type conversion.

In modern C++, if the operation is a cast rather than object construction, static_cast<int>(3.8) is usually clearer.

Decision guide

Ask these questions:

  1. Can I avoid the cast entirely?
    • If yes, do that.
  2. Is this a normal compile-time conversion?
    • Use static_cast.
  3. Am I downcasting through a polymorphic base safely?
    • Use dynamic_cast.
  4. Am I only changing const or volatile?
    • Use const_cast.
  5. Am I doing low-level memory reinterpretation?
    • Use reinterpret_cast.
  6. Am I about to use a C-style cast?
    • Prefer a named cast instead.

Important safety note

A cast can make code compile, but that does not mean it is correct. In C++, many casts can produce undefined behavior if they break the real type rules of the program. The safest approach is to cast as little as possible and choose the narrowest cast that matches your intention.

Mental Model

Think of C++ casts as different kinds of permission slips:

  • static_cast = standard office form. Normal, expected conversions.
  • dynamic_cast = security checkpoint. It checks identity before allowing entry.
  • const_cast = removing a “do not modify” sticker. Dangerous if the object was truly meant to stay unchanged.
  • reinterpret_cast = labeling one box as if it were a completely different box. Sometimes necessary at low level, but very risky.
  • C-style cast = a mystery shortcut. It might do several things, and readers cannot easily tell which one.

Another way to remember it:

  • static_cast: “Convert this in a normal way.”
  • dynamic_cast: “Check what this object really is at runtime.”
  • const_cast: “Change constness only.”
  • reinterpret_cast: “Treat these bits/address as another type.”

The more specific the cast, the clearer your code is.

Take Quiz

Syntax and Examples

Core syntax

static_cast<NewType>(value)
dynamic_cast<NewType>(value)
const_cast<NewType>(value)
reinterpret_cast<NewType>(value)

Older forms:

(NewType)value
NewType(value)

static_cast example

#include <iostream>

int main() {
    double temperature = 23.7;
    int roundedDown = static_cast<int>(temperature);

    std::cout << roundedDown << '\n';
}

static_cast<int>(temperature) converts 23.7 to 23. This is a normal numeric conversion.

dynamic_cast example

#include <iostream>

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

class Circle : public Shape {
public:
    void drawCircle() {
        std::cout << "Drawing circle\n";
    }
};

int main() {
    Shape* shape = new Circle();

    Circle* circle = dynamic_cast<Circle*>(shape);
    if (circle) {
        circle->drawCircle();
    }

    delete shape;
}

Because Shape has a virtual destructor, it is polymorphic. dynamic_cast checks whether shape really points to a Circle.

const_cast example

#include <iostream>

void legacyPrint(char* text) {
    std::cout << text << '\n';
}

int main() {
    const char* message = "Hello";
    legacyPrint(const_cast<char*>(message));
}

This may be used when interacting with an old API that wrongly expects char*. But you must not modify the string literal.

reinterpret_cast example

#include <cstdint>
#include <iostream>

int main() {
    std::uintptr_t address = 1024;
    int* ptr = reinterpret_cast<int*>(address);

    std::cout << ptr << '\n';
}

This compiles, but it does not mean dereferencing ptr is safe. reinterpret_cast does not guarantee the pointed memory is valid.

Prefer named casts over C-style casts

Less clear:

int x = (int)4.9;

Clearer:

int x = static_cast<int>(4.9);

The second form tells the reader exactly what category of cast is intended.

Step by Step Execution

Consider this example:

#include <iostream>

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

class Derived : public Base {
public:
    void hello() {
        std::cout << "Hello from Derived\n";
    }
};

int main() {
    Base* ptr = new Derived();

    Derived* d1 = static_cast<Derived*>(ptr);
    d1->hello();

    Derived* d2 = dynamic_cast<Derived*>(ptr);
    if (d2) {
        d2->hello();
    }

    delete ptr;
}

What happens step by step

  1. Base* ptr = new Derived();

    • A Derived object is created.
    • It is stored in a pointer of type Base*.
    • This is an upcast, and it is safe.
  2. Derived* d1 = static_cast<Derived*>(ptr);

    • The compiler allows the conversion.
    • No runtime type check is performed.
    • This is only safe because ptr really points to a Derived.
  3. d1->hello();

    • This works because the object is truly a Derived.
  4. Derived* d2 = dynamic_cast<Derived*>(ptr);

    • Runtime type information is used to check the real object type.
    • Since the object is a Derived, the cast succeeds.
  5. if (d2)

    • Because the cast succeeded, d2 is not nullptr.
  6. d2->hello();

    • This safely calls the Derived method.
  7. delete ptr;

    • The object is deleted through the base pointer.
    • The virtual destructor ensures correct cleanup.

Why this matters

If ptr had pointed to a plain Base object instead, then:

  • static_cast<Derived*>(ptr) would still compile, but using it as a Derived would be unsafe.
  • dynamic_cast<Derived*>(ptr) would return nullptr, which is much safer.

Real World Use Cases

Numeric and data conversions

static_cast is commonly used when:

  • converting double to int
  • converting enum values to integers
  • converting sizes between numeric types intentionally
double total = 42.8;
int whole = static_cast<int>(total);

Object-oriented code with inheritance

dynamic_cast is useful when working with plugin systems, UI widgets, AST nodes, or game objects where a base pointer may actually refer to different derived types.

Widget* widget = getSelectedWidget();
Button* button = dynamic_cast<Button*>(widget);
if (button) {
    button->click();
}

Interfacing with legacy APIs

const_cast may be needed when an old library takes a non-const pointer even though it does not modify the data.

legacy_api(const_cast<char*>(name.c_str()));

This should be used carefully and only when you know the API will not write to the memory.

Systems and low-level programming

reinterpret_cast appears in code that deals with:

  • memory-mapped devices
  • binary protocols
  • pointer/integer conversions
  • custom allocators
std::uintptr_t raw = get_hardware_address();
auto* reg = reinterpret_cast<volatile std::uint32_t*>(raw);

This kind of code is specialized and requires strong understanding of memory rules.

Take Quiz

Real Codebase Usage

In real C++ codebases, casts often appear as part of defensive patterns.

1. Prefer no cast when possible

Developers often redesign APIs so callers do not need casts.

Instead of this:

void process(Base* obj);

You may see virtual functions used so the correct behavior is chosen automatically.

2. static_cast for explicit intent

When narrowing or converting intentionally, developers use static_cast to show that data loss or transformation is expected.

int size = static_cast<int>(items.size());

This is common when an API expects a specific numeric type.

3. dynamic_cast for safe downcasting at boundaries

In large codebases, dynamic_cast is often used at framework boundaries where a generic interface returns a base pointer, and code must verify the exact subtype.

if (auto* fileLogger = dynamic_cast<FileLogger*>(logger)) {
    fileLogger->rotate();
}

4. const_cast in compatibility layers

It is usually isolated in wrappers around older libraries.

void callLegacy(const std::string& s) {
    legacy_function(const_cast<char*>(s.c_str()));
}

Keeping it in one place reduces the risk of misuse.

5. reinterpret_cast in infrastructure code only

Teams typically restrict it to low-level modules such as serialization, networking internals, memory utilities, or platform abstraction layers.

6. Guard clauses with dynamic_cast

A common pattern is to check the result immediately.

auto* derived = dynamic_cast<Derived*>(basePtr);
if (!derived) {
    return;
}

derived->run();

This avoids unsafe use after a failed cast.

7. Replacing casts with better abstractions

Experienced developers often treat frequent casting as a design smell. If code constantly needs to cast objects to discover their real type, the class design may need improvement.

Common Mistakes

1. Using static_cast for unsafe downcasting

Broken example:

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

class Derived : public Base {
public:
    void hello() {}
};

Base base;
Base* ptr = &base;
Derived* d = static_cast<Derived*>(ptr); // dangerous

This compiles, but ptr does not actually point to a Derived. Using d as a Derived* is undefined behavior.

Use dynamic_cast when you need runtime checking.

2. Removing const and then modifying truly const data

Broken example:

const int value = 10;
int* p = const_cast<int*>(&value);
*p = 20; // undefined behavior

If the original object was declared const, modifying it after const_cast is not allowed.

3. Assuming reinterpret_cast performs a safe conversion

Broken example:

double x = 3.14;
int* p = reinterpret_cast<int*>(&x);

This does not convert a double into an int. It just treats the same memory as a different type, which can violate aliasing and produce undefined behavior.

4. Using C-style casts because they are shorter

Broken example:

Derived* d = (Derived*)ptr;

This hides whether the code is doing a static_cast, const_cast, reinterpret_cast, or a combination. Prefer named casts.

5. Forgetting that dynamic_cast needs a polymorphic base

Broken example:

class Base {};
class Derived : public Base {};

Base* ptr = new Derived();
Derived* d = dynamic_cast<Derived*>(ptr); // error: Base is not polymorphic

Fix it by adding a virtual function, commonly a virtual destructor.

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

6. Using casts instead of fixing the design

If you often write code like this:

if (auto* cat = dynamic_cast<Cat*>(animal)) {
    // ...
} else if (auto* dog = dynamic_cast<Dog*>(animal)) {
    // ...
}

it may mean polymorphism should be used instead:

animal->speak();

This is often cleaner and more maintainable.

Comparisons

Cast comparison table

CastMain purposeRuntime checkSafety levelTypical use
static_castNormal conversionsNoMediumNumeric conversions, upcasts, trusted downcasts
dynamic_castSafe downcasting in polymorphismYesHigherChecking actual derived type
const_castChange const/volatile qualifiersNoLow to mediumLegacy APIs, qualifier adjustment
reinterpret_castLow-level reinterpretationNoLowSystems code, address manipulation
C-style castMultiple possible conversionsNoLowOld code; generally avoid
Function-style castConstruction or cast-like conversionNoLow to mediumConstructor syntax; less clear for casts

static_cast vs dynamic_cast

SituationBetter choiceWhy
Convert double to intstatic_castNormal compile-time conversion
Upcast Derived* to Base*Usually no cast neededHappens implicitly
Downcast when runtime type is uncertaindynamic_castChecks actual object type
Downcast when you are absolutely certainstatic_castFaster, but unsafe if wrong

Named casts vs C-style casts

StyleExampleWhy it matters
Named caststatic_cast<int>(x)Clear intent
C-style cast(int)xAmbiguous and easier to misuse
Function-style castint(x)Can look like construction, less explicit

Quick selection guide

  • Use static_cast for normal conversions.
  • Use dynamic_cast for safe runtime-checked downcasting.
  • Use const_cast only for changing qualifiers.
  • Use reinterpret_cast only for low-level memory work.
  • Avoid C-style casts in modern C++.
Take Quiz

Cheat Sheet

Quick rules

  • Best rule: avoid casting unless necessary.
  • Prefer named casts over C-style casts.
  • Use the most specific cast for your intent.

Which cast should I use?

  • Normal conversion? -> static_cast
  • Need runtime type check in inheritance? -> dynamic_cast
  • Only changing const or volatile? -> const_cast
  • Low-level bit/address reinterpretation? -> reinterpret_cast
  • Old (type)value cast? -> usually replace with a named cast

Syntax

static_cast<T>(value)
dynamic_cast<T>(value)
const_cast<T>(value)
reinterpret_cast<T>(value)

Safety notes

  • static_cast

    • good for explicit numeric conversions
    • no runtime safety for downcasts
  • dynamic_cast

    • requires a polymorphic base class
    • pointer cast fails with nullptr
    • reference cast fails with exception
  • const_cast

    • only changes cv-qualifiers
    • modifying an originally const object is undefined behavior
  • reinterpret_cast

    • very low-level
    • easy to create undefined behavior
    • avoid in normal application code

Avoid these when possible

(type)value
Type(value)

They are less explicit than named casts.

Common examples

int n = static_cast<int>(3.9);
Derived* d = dynamic_cast<Derived*>(basePtr);
if (d) {
    d->run();
}
legacy_api(const_cast<char*>(text));
auto p = reinterpret_cast<std::uint32_t*>(address);

FAQ

What is the safest cast in C++?

There is no single safest cast for every case, but static_cast is the usual safe choice for normal explicit conversions, and dynamic_cast is safest for uncertain downcasts in polymorphic class hierarchies.

Why are C-style casts discouraged in C++?

Because they are ambiguous. A C-style cast can perform several different kinds of conversions, making code harder to read and easier to misuse.

When should I use dynamic_cast instead of static_cast?

Use dynamic_cast when converting from a base type to a derived type and the real object type may vary at runtime.

Can const_cast remove const safely?

It can remove the qualifier syntactically, but modifying an object that was originally declared const is undefined behavior.

Is reinterpret_cast portable?

Often not fully. It is closely tied to platform details, memory layout, and representation rules, so it should be used carefully and sparingly.

Does dynamic_cast require virtual functions?

Yes, the base class must be polymorphic, which usually means it has at least one virtual function such as a virtual destructor.

Should I ever use function-style casts like int(x)?

They are valid, but for explicit type conversion, named casts like static_cast<int>(x) are usually clearer.

Is frequent casting a bad sign?

Often, yes. Heavy use of casts can indicate that the design could be improved so objects and functions work through clearer interfaces.

Take Quiz

Mini Project

Description

Build a small C++ program that demonstrates the four main named casts in realistic situations. The project uses numeric conversion, safe downcasting, qualifier adjustment for a legacy function, and a low-level pointer reinterpretation example. The goal is not to encourage unnecessary casting, but to help you recognize when each cast is intended to be used.

Goal

Create a program that prints examples of static_cast, dynamic_cast, const_cast, and reinterpret_cast, and clearly shows which operations are safe and which require caution.

Requirements

  • Create one example that converts a double to an int using static_cast.
  • Create a polymorphic base class and safely downcast with dynamic_cast.
  • Call a legacy-style function using const_cast without modifying the original data.
  • Show a reinterpret_cast example that converts a pointer to an integer address and prints it.
  • Print clear output labels so each cast example is easy to identify.
Take Quiz

Keep learning

Related questions

View more