Question

When to Use reinterpret_cast vs static_cast in C++

cppcastingreinterpret-cast

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 int to double
  • 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_cast when the conversion is supported by the type system
  • use reinterpret_cast only 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_cast is 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_cast is 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.

Take Quiz

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* to void* is a standard pointer conversion.
  • void* back to MyClass* is also supported when the void* originally came from that object pointer.
  • static_cast expresses 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:

  1. MyClass obj;

    • A MyClass object is created on the stack.
  2. void* raw = static_cast<void*>(&obj);

    • &obj is a MyClass*.
    • It is converted to void*.
    • The address does not change; only the type used to describe it becomes generic.
  3. MyClass* typed = static_cast<MyClass*>(raw);

    • The generic pointer is converted back to MyClass*.
    • This is valid because raw originally came from a MyClass*.
  4. 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.

Take Quiz

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_cast or dynamic_cast depending on direction and safety needs

Comparisons

CastTypical useChecked by type system?Safe for void* ↔ object pointer?Notes
static_castWell-defined conversionsYes, within language rulesYesBest choice for void* and object pointer conversions
reinterpret_castLow-level reinterpretationVery limitedCan compile, but not preferred hereUse only when you need bit-level or low-level pointer reinterpretation
dynamic_castSafe polymorphic downcastingYes, at runtimeNoUsed with inheritance and virtual functions
C-style castOlder catch-all cast syntaxHard to tellWorks in some casesAvoid 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.

Take Quiz

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_cast can do it, use static_cast.
  • reinterpret_cast does 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_cast may compile even when the resulting use is undefined behavior.
  • static_cast from void* does not verify the runtime type.
  • For inheritance downcasts, dynamic_cast may be safer than static_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.

Take Quiz

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, and destroy functions with extern "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.
Take Quiz

Keep learning

Related questions

View more