Question
I am confused about when to use reinterpret_cast versus static_cast in C++.
From what I understand, static_cast is generally used when the conversion is well-defined and can be checked at compile time. It is also the kind of cast the compiler commonly performs for many implicit conversions.
reinterpret_cast seems to be used mainly in cases like these:
- converting integer types to pointer types, and vice versa
- converting one pointer type to another pointer type
My understanding is that this kind of cast is often low-level, potentially non-portable, and should usually be avoided.
The specific case I am unsure about is C and C++ interoperability. I am calling C++ code from C, and the C code needs to store a C++ object pointer as a void*. Which cast should be used to convert between void* and a class pointer type?
For example:
class MyClass {
public:
void hello();
};
void* store_object(MyClass* obj) {
// Which cast is correct here?
return ???;
}
MyClass* load_object(void* ptr) {
// Which cast is correct here?
return ???;
}
I have seen both static_cast and reinterpret_cast used for this. It seems like static_cast may be preferable if the conversion is well-defined, but I also read that reinterpret_cast is used for pointer-to-pointer conversions. Which one is correct in this situation, and why?
Short Answer
By the end of this page, you will understand the difference between static_cast and reinterpret_cast in C++, especially for pointer conversions. You will also learn the correct cast to use when converting between void* and a class pointer in C/C++ interoperability code, and why reinterpret_cast is usually reserved for lower-level, riskier conversions.
Concept
In C++, different cast operators communicate different intentions and levels of safety.
static_cast is for well-defined conversions that the language understands. These include things like:
- numeric conversions such as
inttodouble - converting a derived class pointer to a base class pointer
- converting
void*back to the original object pointer type - calling explicit constructors or conversion operators
reinterpret_cast is for low-level bit-pattern reinterpretation. It tells the compiler: “treat these bits as if they were another type.” It does not usually perform a meaningful semantic conversion. Instead, it reinterprets the value at a lower level.
That is why reinterpret_cast is commonly associated with:
- pointer type punning
- converting pointers to integers and back
- interacting with low-level APIs, memory-mapped hardware, or serialization hacks
For your specific case, converting between void* and MyClass* is a standard, well-defined pointer conversion in C++. That means static_cast is the correct choice.
Example:
void* p = static_cast<void*>(obj);
MyClass* obj2 = static_cast<MyClass*>(p);
This works because void* is the generic object pointer type in both C and C++.
Why this matters in real programming:
- It makes your intent clear.
- It uses the safest cast that matches the language rules.
- It avoids unnecessary low-level casts that may hide mistakes.
- It makes code easier to review and maintain.
A useful rule is:
- use
static_castwhen the conversion is supported by the type system - use
reinterpret_castonly when you truly need low-level reinterpretation
So although reinterpret_cast can often compile for pointer conversions, that does not make it the best or most correct choice.
Mental Model
Think of C++ casts like different kinds of permission slips.
static_castis like changing a label in a way the system understands. For example, moving a package from a box labeled “generic object” (void*) back into a box labeled “MyClass*`. The object is still the same object.reinterpret_castis like telling someone to treat a package of screws as if it were a package of batteries just because the box is the same size. Sometimes low-level code does this on purpose, but it is risky and easy to misuse.
For void* and class pointers, you are not changing what the object is. You are just temporarily storing it in a generic container. That is exactly the kind of job static_cast is meant for.
Syntax and Examples
The key syntax is:
static_cast<T>(expression)
reinterpret_cast<T>(expression)
Correct use for void* and class pointers
class MyClass {
public:
void hello() {}
};
void* store_object(MyClass* obj) {
return static_cast<void*>(obj);
}
MyClass* load_object(void* ptr) {
return static_cast<MyClass*>(ptr);
}
Why this is correct
MyClass*tovoid*is a standard pointer conversion.void*back toMyClass*is also supported when thevoid*originally came from that object pointer.static_castexpresses that this is a normal, language-supported conversion.
Example of reinterpret_cast
int value = 65;
int* p = &value;
long raw = reinterpret_cast<long>(p);
This is a low-level conversion. It may compile, but it is not the same kind of safe, semantic conversion as static_cast.
Another risky reinterpret_cast example
int x = 42;
float* fp = reinterpret_cast<float*>(&x);
This does not convert the integer 42 into the floating-point 42.0. It tells the compiler to treat the memory holding an int as if it were a float. That is a very different operation and is usually wrong.
C API wrapper example
This is a common real-world pattern:
class Engine {
public:
void run() {}
};
extern "C" void* engine_create() {
return static_cast<void*>(new Engine());
}
extern "C" void engine_run(void* handle) {
Engine* engine = static_cast<Engine*>(handle);
engine->run();
}
extern "C" void engine_destroy(void* handle) {
Engine* engine = static_cast<Engine*>(handle);
delete engine;
}
This lets C code hold an opaque handle as void* while the actual object is managed in C++.
Step by Step Execution
Consider this example:
class MyClass {
public:
void hello() {}
};
int main() {
MyClass obj;
void* raw = static_cast<void*>(&obj);
MyClass* typed = static_cast<MyClass*>(raw);
typed->hello();
}
Step by step:
-
MyClass obj;- A
MyClassobject is created on the stack.
- A
-
void* raw = static_cast<void*>(&obj);&objis aMyClass*.- It is converted to
void*. - The address does not change; only the type used to describe it becomes generic.
-
MyClass* typed = static_cast<MyClass*>(raw);- The generic pointer is converted back to
MyClass*. - This is valid because
raworiginally came from aMyClass*.
- The generic pointer is converted back to
-
typed->hello();- The program uses the recovered typed pointer to call a member function.
Important detail:
If raw did not originally point to a MyClass object, converting it to MyClass* and using it would be undefined behavior.
For example, this is wrong:
int x = 5;
void* raw = &x;
MyClass* typed = static_cast<MyClass*>(raw); // compiles, but wrong to use
The cast may compile, but the pointer does not actually point to a MyClass object.
Real World Use Cases
This concept appears often in practical C++ programming.
1. C and C++ interoperability
C APIs cannot directly store C++ class types, so they often use opaque handles:
extern "C" void* widget_create();
extern "C" void widget_destroy(void* handle);
Internally, the handle is really a C++ object pointer.
2. Generic callback user data
Many C libraries let you pass a void* context pointer:
void register_callback(void (*fn)(void*), void* user_data);
C++ code stores an object pointer in user_data and later converts it back.
3. Plugin systems and native bindings
Frameworks that bridge languages or modules often pass around untyped handles and recover typed pointers internally.
4. Resource wrappers
A graphics, audio, or database library may expose opaque handles in public C APIs while managing rich C++ objects under the hood.
In all of these cases, static_cast is the normal choice for void* conversions.
Real Codebase Usage
In real projects, developers usually avoid exposing C++ classes directly across a C boundary. Instead, they use an opaque handle pattern.
Common pattern: opaque handle API
class Connection {
public:
bool open() { return true; }
};
extern "C" void* connection_create() {
return static_cast<void*>(new Connection());
}
extern "C" int connection_open(void* handle) {
if (!handle) {
return 0;
}
Connection* conn = static_cast<Connection*>(handle);
return conn->open() ? 1 : 0;
}
extern "C" void connection_destroy(void* handle) {
if (!handle) {
return;
}
Connection* conn = static_cast<Connection*>(handle);
delete conn;
}
Patterns commonly used with this
- Guard clauses: check
if (!handle)before casting or using the pointer. - Early returns: return immediately on invalid handles.
- Validation: in larger systems, some code tracks active handles to detect misuse.
- Encapsulation: only a small part of the codebase performs the cast; the rest uses typed C++ pointers.
- Error handling: C-facing APIs often return integer status codes instead of throwing exceptions.
Why reinterpret_cast is uncommon here
Using reinterpret_cast<MyType*>(handle) may compile, but it signals a lower-level conversion than necessary. In code review, that can make readers think something unusual or risky is happening. static_cast better reflects the intended and supported conversion.
Common Mistakes
1. Using reinterpret_cast when static_cast is enough
Broken style:
MyClass* obj = reinterpret_cast<MyClass*>(ptr);
Better:
MyClass* obj = static_cast<MyClass*>(ptr);
Why: void* to object pointer is a standard conversion, so static_cast is clearer and safer in intent.
2. Casting a void* to the wrong type
int x = 10;
void* ptr = &x;
MyClass* obj = static_cast<MyClass*>(ptr); // compiles
obj->hello(); // undefined behavior
Avoid this by making sure the void* really came from the same original type.
3. Assuming reinterpret_cast performs value conversion
Broken assumption:
int x = 42;
float* f = reinterpret_cast<float*>(&x);
This does not convert 42 into 42.0f. It just treats the bytes of x as if they were a float.
If you want numeric conversion, use static_cast:
int x = 42;
float f = static_cast<float>(x);
4. Forgetting null checks in C API wrappers
extern "C" void work(void* handle) {
MyClass* obj = static_cast<MyClass*>(handle);
obj->hello();
}
Safer:
extern "C" void work(void* handle) {
if (!handle) {
return;
}
MyClass* obj = static_cast<MyClass*>(handle);
obj->hello();
}
5. Mixing cast styles inconsistently
Avoid mixing C-style casts, static_cast, and reinterpret_cast for the same kind of conversion. Prefer explicit C++ casts so the intent is obvious.
6. Treating all pointer-to-pointer conversions as equally safe
Not all pointer conversions are the same:
void*to original object pointer type: commonly valid- unrelated pointer types via
reinterpret_cast: risky - base/derived conversions: use
static_castordynamic_castdepending on direction and safety needs
Comparisons
| Cast | Typical use | Checked by type system? | Safe for void* ↔ object pointer? | Notes |
|---|---|---|---|---|
static_cast | Well-defined conversions | Yes, within language rules | Yes | Best choice for void* and object pointer conversions |
reinterpret_cast | Low-level reinterpretation | Very limited | Can compile, but not preferred here | Use only when you need bit-level or low-level pointer reinterpretation |
dynamic_cast | Safe polymorphic downcasting | Yes, at runtime | No | Used with inheritance and virtual functions |
| C-style cast | Older catch-all cast syntax | Hard to tell | Works in some cases | Avoid in modern C++ because it can hide dangerous conversions |
static_cast vs reinterpret_cast
void* p = static_cast<void*>(obj); // standard conversion
MyClass* q = static_cast<MyClass*>(p); // standard conversion
versus
MyClass* q = reinterpret_cast<MyClass*>(p); // compiles, but stronger and less appropriate
Use reinterpret_cast only when static_cast cannot express the operation you need.
static_cast vs dynamic_cast
If inheritance is involved:
Base* b = new Derived();
Derived* d1 = static_cast<Derived*>(b); // no runtime check
Derived* d2 = dynamic_cast<Derived*>(b); // runtime-checked if Base is polymorphic
This is a separate issue from void* conversions, but it helps show that each cast has a different purpose.
Cheat Sheet
// Generic syntax
static_cast<T>(expr)
reinterpret_cast<T>(expr)
Use static_cast for
- numeric conversions
void*to original object pointer type- object pointer to
void* - base/derived conversions that are statically valid
- explicit constructor or conversion operations
void* p = static_cast<void*>(obj);
MyClass* obj2 = static_cast<MyClass*>(p);
Use reinterpret_cast for
- low-level pointer reinterpretation
- pointer-to-integer or integer-to-pointer conversions
- rare systems programming cases
std::uintptr_t raw = reinterpret_cast<std::uintptr_t>(ptr);
Rules of thumb
- Prefer the least powerful cast that works.
- If
static_castcan do it, usestatic_cast. reinterpret_castdoes not mean “convert safely.” It means “reinterpret at a low level.”void*is a generic object pointer, not a magic type-safe container.- Casting back from
void*is only safe if it really points to the correct original type.
Edge cases
reinterpret_castmay compile even when the resulting use is undefined behavior.static_castfromvoid*does not verify the runtime type.- For inheritance downcasts,
dynamic_castmay be safer thanstatic_cast. - Avoid C-style casts in modern C++ because they can silently do several kinds of conversions at once.
FAQ
Should I use static_cast or reinterpret_cast for void* to class pointer in C++?
Use static_cast. Converting between void* and an object pointer type is a standard, well-defined conversion.
Why does reinterpret_cast also seem to work for void* conversions?
Because it is very permissive for pointer conversions. But that does not make it the best tool. static_cast better expresses the actual language-supported conversion.
Is converting a class pointer to void* safe?
Yes. Converting an object pointer to void* and back to the same original type is a normal and common pattern.
Can static_cast from void* detect the wrong type at runtime?
No. It only performs the conversion. You must ensure that the void* really points to the expected object type.
When should I really use reinterpret_cast in C++?
Use it for low-level operations where you intentionally need to reinterpret bits or raw addresses, such as some systems programming tasks.
Is reinterpret_cast always non-portable?
Not always, but it often enters implementation-dependent or risky territory. That is why it should be used sparingly.
Should I use C-style casts instead?
Usually no. C-style casts are harder to read and can hide dangerous conversions. Prefer explicit C++ casts like static_cast and reinterpret_cast.
What cast should I use in a C API that stores opaque handles?
Use static_cast<void*>(obj) when storing the handle and static_cast<MyType*>(handle) when recovering it.
Mini Project
Description
Build a tiny C-compatible wrapper around a C++ class using an opaque void* handle. This demonstrates the correct use of static_cast when storing and recovering a C++ object pointer across a C-style API boundary.
Goal
Create, use, and destroy a C++ object through a C-style API that exposes only void* handles.
Requirements
- Define a simple C++ class with at least one method.
- Expose
create,use, anddestroyfunctions withextern "C". - Store the object as a
void*in the public API. - Convert between
void*and the class pointer using the correct cast. - Handle null pointers safely in the API functions.
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.