Four Types of Type Conversion Operators in C++

Four Types of Type Conversion Operators in C++

In C++, there are four type conversion operators:<span>static_cast</span>, <span>dynamic_cast</span>, <span>const_cast</span> and <span>reinterpret_cast</span>.

They are used in different scenarios, providing safer type conversion methods, replacing the simple type casting in C (i.e., <span>(type)expression</span>).

Conversion Operator Type Check Timing Safety Usage
static_cast Related Types Compile Usually Safe Basic type conversion, upcasting, explicit call to conversion functions
dynamic_cast Polymorphic Types Runtime Safe Safe downcasting and cross-casting
const_cast const/volatile Attributes Compile Unsafe Add or remove const/volatile qualifiers
reinterpret_cast Any Unrelated Types Compile Unsafe Low-level, hardware-related bit reinterpretation

Why Four Casts Are Needed

In C, any type conversion is done using <span>(type)value</span>.

While this is convenient, it brings a problem: the intent of the conversion is unclear.

For example: <span>(int*)ptr</span> This conversion could mean:

  1. I want to remove the const attribute
  2. I just want to safely convert a base class pointer to a derived class pointer
  3. I want to perform a low-level, reinterpretation of bytes

The compiler cannot distinguish the true intent. Due to the lack of a runtime environment like modern languages such as Python, these issues are often overlooked, and once the code has errors, troubleshooting becomes very difficult.

1. static_cast

<span>static_cast</span> isthe most general and commonly used conversion.

It performs type checking at compile time but does not have runtime type checking (RTTI). It is used for conversions that the compiler considers “reasonable to some extent”.

Basic data type conversions: such as <span>int</span> to <span>double</span>, <span>enum</span> to <span>int</span>.

  • Upcasting: Converting a derived class pointer/reference to a base class pointer/reference.
  • Downcasting: Converting a base class pointer/reference to a derived class pointer/reference. Unsafe! The compiler cannot know at compile time whether the pointer actually points to a base class object or a derived class object. If it does not point to the target type, the behavior is undefined (UB). It should be replaced with <span>dynamic_cast</span><span><span> for safe downcasting.</span></span>
  • Any class type conversion that has a conversion constructor or conversion function.

Example:

int i =10;
double d =static_cast<double>(i);// Basic type conversion int to double

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

Derived der;
Base* basePtr =static_cast<Base*>(&der);// Upcasting

Base base;
Derived* derPtr =static_cast<Derived*>(&base);// Downcasting, may fail at runtime.

void* vPtr =&i;
int* iPtr =static_cast<int*>(vPtr);// Convert void* back to original type

2. dynamic_cast

<span>dynamic_cast</span> is known asdynamic conversion, used for safe downcasting or cross-casting along the inheritance chain.

It is specifically used for pointer or reference conversions in an inheritance hierarchy (polymorphic types) that has virtual functions.It performs type checking at runtime, thus incurs a performance overhead.

Safe downcasting: Checks whether a base class pointer actually points to a derived class object. If so, the conversion is successful; otherwise, it returns <span>nullptr</span><span><span> for pointers and throws </span></span><code><span>std::bad_cast</span><span><span> exception for references.</span></span>

It requires runtime type information (RTTI), so the base class must have at least one virtual function.

Example:

class Base{
virtual void dummy(){
}
};
class Derived:public Base {
};

Base* basePtr =new Derived;// Base class pointer actually points to derived class object

// Downcasting
Derived* derPtr =dynamic_cast<Derived*>(basePtr);
if(derPtr){
// Conversion successful, basePtr indeed points to Derived object
}else{
// Conversion failed, basePtr does not point to Derived object
}

Base actualBase;
Derived* badPtr =dynamic_cast<Derived*>(&actualBase);// Failed, badPtr will be nullptr

// Reference
try{
    Derived& derRef =dynamic_cast<Derived&>(*basePtr);
}catch(const std::bad_cast& e){
// Conversion failed
}

3. const_cast

<span>const_cast</span> is used to handle variables that are not originally const but are declared as const, or to call some old library functions that do not support const correctness.

Attempting to modify an object that is originally declared as const will lead to undefined behavior, and in practice, this conversion should be avoided as much as possible.

You can only use const_cast to remove the constness of a “pointer to const”, and cannot modify a true constant.

Example:

// Legal and safe usage scenario
void print(char* str){// An old function that does not accept const char*
    cout << str << endl;
}

const char* greeting ="Hello";
// print(greeting); // Error! Cannot pass const char* to char*
print(const_cast<char*>(greeting));// Correct, removed const

// Attempt to modify const constant (Don't do this)
const int i =42;
const int* constPtr =&i;
int* modifiable =const_cast<int*>(constPtr);
*modifiable =100;// Undefined behavior! Because i itself is a constant.

4. reinterpret_cast

<span>reinterpret_cast</span> provides low-level, bitwise reinterpretation. It is used for conversions between completely unrelated types, typically in low-level programming, such as drivers, serialization, network protocols, etc.

  • Conversion between pointers and integers (e.g., converting a pointer address to a uintptr_t).
  • Converting a pointer of one type to a pointer of another completely unrelated type (e.g., Foo to Bar).

This is the most dangerous conversion. Using it can easily lead to undefined behavior, and the code is not portable.

Its result usually needs to be converted back to the original type before use.

Example:

int i =0x11451419;
int* iPtr =&i;

// Force interpret int* as char*, used to check memory bytes
char* charPtr =reinterpret_cast<char*>(iPtr);
for(size_t n =0; n <sizeof(int);++n){
    cout <<static_cast<int>(charPtr[n])<<" ";// Output each byte in memory
}

// Conversion between function pointers
typedef void(*FuncPtr)();
FuncPtr funcPtr =reinterpret_cast<FuncPtr>(someFunction);// Assume someFunction has a different signature

// Pointer to integer (address value)
uintptr_t addr =reinterpret_cast<uintptr_t>(iPtr);

Four Types of Type Conversion Operators in C++


Leave a Comment