C-style casts, static_cast, dynamic_cast, reinterpret_cast, const_cast, and volatile.
C-style casts force the compiler to treat a value as a different type. They use the familiar parenthesis syntax from C:
int* intArr = (int*) somePointer; // forces compiler to assume int*
reinterpret_cast and const_cast — without making it obvious which one. They also can't be searched for easily in a codebase.All C++ casts share the same syntax:
ResultType result = cast_operator<ResultType>(objectToCast);
| Cast | Use case | Safety |
|---|---|---|
static_cast | Related types, built-in conversions | Compile-time checks only |
dynamic_cast | Downcasting in an inheritance hierarchy | Runtime checked — safest for polymorphic types |
reinterpret_cast | Completely unrelated pointer/type reinterpretation | No checks — most dangerous |
const_cast | Add or remove const qualifier | Careful — removing const on a truly const object is UB |
static_caststatic_cast performs conversions between related types at compile time. It is used for:
int → double)Base* objBase = new Base();
// Downcast: Base* → Derived* (unsafe if objBase doesn't point to a Derived)
Derived* objDer = static_cast<Derived*>(objBase);
// Built-in conversion
double d = static_cast<double>(5); // 5 → 5.0
static_cast performs compile-time checks only. It is safer than a C-style cast, but it cannot confirm at runtime that a downcast is actually valid. Use dynamic_cast when you need that guarantee.dynamic_castdynamic_cast is the opposite of static_cast in terms of timing — it executes at runtime using RTTI (Run-Time Type Information) to verify the cast is valid. It works on pointers and references to polymorphic types (types with at least one virtual function).
Base* objBase = new Derived();
// Runtime check — returns nullptr if the cast is invalid
Derived* objDer = dynamic_cast<Derived*>(objBase);
if (objDer) {
// Cast succeeded — objBase really did point to a Derived
} else {
// Cast failed — objBase does not point to a Derived
}
nullptr on failure (pointer) or throws std::bad_cast (reference), which you can handlereinterpret_castreinterpret_cast forces a reinterpretation of the underlying bits — it does not actually change the binary representation of the value, just tells the compiler to treat those bytes as a different type. It can convert:
int x = 65;
char* c = reinterpret_cast<char*>(&x); // reinterpret the bytes of x as a char*
reinterpret_cast bypasses the type system entirely. It is similar to a C-style cast and provides no safety guarantees. Legitimate uses are rare — mostly hardware register access, serialization, and certain platform-specific tricks.const_castconst_cast is the only C++ cast that can add or remove the const qualifier from a type. A common use case is calling a non-const function from within a const method when you know it's safe:
void nonConstFunc(int* p) { /* modifies p */ }
void myConstMethod() const {
// *this is const here, but we need a non-const pointer to _data
nonConstFunc(const_cast<int*>(_data));
}
const from an object that was originally declared const and then writing to it is undefined behavior. const_cast is only valid when the underlying object is not truly const — for example, a const reference to a non-const variable.const_cast can also add or remove the volatile qualifier in the same way it handles const.volatilevolatile tells the compiler not to optimize out reads or writes to a variable — every access must actually go to memory. It's used for memory-mapped hardware registers and shared memory in some multi-threaded contexts:
volatile int* hwReg = (volatile int*)0xDEADBEEF;
*hwReg = 1; // compiler will NOT optimize this away
volatile does not provide thread-safety or atomicity — it only prevents compiler optimization of the access. For multi-threaded synchronization use std::atomic or a mutex instead.