Question
When to Use static_cast, dynamic_cast, const_cast, and reinterpret_cast in C++
Question
In C++, what are the correct use cases for these casting forms?
static_castdynamic_castconst_castreinterpret_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_castis for normal, compile-time conversions.dynamic_castis for safe casting in inheritance hierarchies at runtime.const_castis for adding or removingconstorvolatilequalifiers.reinterpret_castis 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:
- Avoid casting if you can. Prefer designing code so conversion is unnecessary.
- If you must cast, use the most specific named cast.
- 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
doubletoint - converting
void*to a typed pointer when you know the real type - upcasting in inheritance (
Derived*toBase*) - 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_castconst_castreinterpret_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:
- Can I avoid the cast entirely?
- If yes, do that.
- Is this a normal compile-time conversion?
- Use
static_cast.
- Use
- Am I downcasting through a polymorphic base safely?
- Use
dynamic_cast.
- Use
- Am I only changing
constorvolatile?- Use
const_cast.
- Use
- Am I doing low-level memory reinterpretation?
- Use
reinterpret_cast.
- Use
- 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.
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
-
Base* ptr = new Derived();- A
Derivedobject is created. - It is stored in a pointer of type
Base*. - This is an upcast, and it is safe.
- A
-
Derived* d1 = static_cast<Derived*>(ptr);- The compiler allows the conversion.
- No runtime type check is performed.
- This is only safe because
ptrreally points to aDerived.
-
d1->hello();- This works because the object is truly a
Derived.
- This works because the object is truly a
-
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.
-
if (d2)- Because the cast succeeded,
d2is notnullptr.
- Because the cast succeeded,
-
d2->hello();- This safely calls the
Derivedmethod.
- This safely calls the
-
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 aDerivedwould be unsafe.dynamic_cast<Derived*>(ptr)would returnnullptr, which is much safer.
Real World Use Cases
Numeric and data conversions
static_cast is commonly used when:
- converting
doubletoint - 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.
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
| Cast | Main purpose | Runtime check | Safety level | Typical use |
|---|---|---|---|---|
static_cast | Normal conversions | No | Medium | Numeric conversions, upcasts, trusted downcasts |
dynamic_cast | Safe downcasting in polymorphism | Yes | Higher | Checking actual derived type |
const_cast | Change const/volatile qualifiers | No | Low to medium | Legacy APIs, qualifier adjustment |
reinterpret_cast | Low-level reinterpretation | No | Low | Systems code, address manipulation |
| C-style cast | Multiple possible conversions | No | Low | Old code; generally avoid |
| Function-style cast | Construction or cast-like conversion | No | Low to medium | Constructor syntax; less clear for casts |
static_cast vs dynamic_cast
| Situation | Better choice | Why |
|---|---|---|
Convert double to int | static_cast | Normal compile-time conversion |
Upcast Derived* to Base* | Usually no cast needed | Happens implicitly |
| Downcast when runtime type is uncertain | dynamic_cast | Checks actual object type |
| Downcast when you are absolutely certain | static_cast | Faster, but unsafe if wrong |
Named casts vs C-style casts
| Style | Example | Why it matters |
|---|---|---|
| Named cast | static_cast<int>(x) | Clear intent |
| C-style cast | (int)x | Ambiguous and easier to misuse |
| Function-style cast | int(x) | Can look like construction, less explicit |
Quick selection guide
- Use
static_castfor normal conversions. - Use
dynamic_castfor safe runtime-checked downcasting. - Use
const_castonly for changing qualifiers. - Use
reinterpret_castonly for low-level memory work. - Avoid C-style casts in modern C++.
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
constorvolatile? ->const_cast - Low-level bit/address reinterpretation? ->
reinterpret_cast - Old
(type)valuecast? -> 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
constobject 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.
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
doubleto anintusingstatic_cast. - Create a polymorphic base class and safely downcast with
dynamic_cast. - Call a legacy-style function using
const_castwithout modifying the original data. - Show a
reinterpret_castexample that converts a pointer to an integer address and prints it. - Print clear output labels so each cast example is easy to identify.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.