Skip to content

Expressions and Casting

New to this topic?

C++ introduces many different ways to cast over C style based casting that offer more control and safety. Let's discuss them one by one and compare them to C-style casts.

Implicit Conversions

C++ automatically converts between certain types without you explicitly asking:

cpp
int x = 42;
double y = x;        // Implicit conversion: int → double
char c = 65;         // Implicit conversion: int → char ('A')
bool flag = x;       // Implicit conversion: int → bool (true if non-zero)

Explicit Casting

When you need to force a conversion that C++ wouldn't do automatically, you use explicit casting:

cpp
double pi = 3.14159;
int rounded = (int)pi;           // C-style cast: double → int
int modern = static_cast<int>(pi); // C++ style cast: double → int

C-Style Casts vs C++ Casts

C-Style Casts (Avoid in C++)

cpp
int x = 42;
double* ptr = (double*)&x;  // Dangerous! Converting int* to double*

Problems with C-style casts:

  • They can hide dangerous conversions. They don't really care about whether the cast you're doing is valid or not. They will just convert the data at the bit level, which could lead to undefined behavior.
  • They're not explicit about what type of conversion is happening

C++ Casts (Preferred)

C++ provides four specific cast operators that make your intent clear:

cpp
int x = 42;
// double* ptr = static_cast<double*>(&x);  // Compile error! Good!
double* ptr = reinterpret_cast<double*>(&x); // Explicitly dangerous

Think of it like: C-style casts are like saying "trust me, I know what I'm doing" without explaining why. C++ casts are like saying "I want to do this specific type of conversion" - much clearer and safer.

static_cast - The Safe Cast

static_cast is a compile time cast and checks the validity of types and whether they can be converted to each other before allowing you to cast. If the cast is invalid, a compilation error is generated.

cpp
// Numeric conversions
double pi = 3.14159;
int rounded = static_cast<int>(pi);  // 3

// Upcasting in inheritance (safe)
class Base {};
class Derived : public Base {};
Derived d;
Base* b = static_cast<Base*>(&d);  // Safe upcast

// Converting void* to other pointer types
void* ptr = malloc(100);
int* int_ptr = static_cast<int*>(ptr);

// Enum to int
enum Color { Red, Green, Blue };
int color_value = static_cast<int>(Red);  // 0

When to use static_cast:

  • Numeric conversions (int to double, etc.)
  • Upcasting in inheritance hierarchies
  • Converting void* to other pointer types
  • Enum to int conversions

const_cast - Removing Const

const_cast removes or adds the const qualifier:

cpp
void printValue(const int* ptr) {
    std::cout << *ptr << std::endl;
}

void modifyValue(int* ptr) {
    *ptr = 100;
}

int main() {
    int x = 42;
    const int* const_ptr = &x;

    // Call non-const function on const pointer
    modifyValue(const_cast<int*>(const_ptr));

    return 0;
}

⚠️ DANGER: Modifying an originally const object through const_cast results in undefined behavior:

cpp
const int x = 42;
int* ptr = const_cast<int*>(&x);
*ptr = 100;  // UNDEFINED BEHAVIOR! x was originally const

reinterpret_cast - The Dangerous Cast

reinterpret_cast converts between completely unrelated types:

cpp
int x = 42;
double* ptr = reinterpret_cast<double*>(&x);  // Dangerous!

⚠️ DANGER: This can lead to undefined behavior and type safety violations:

cpp
int x = 42;
double* ptr = reinterpret_cast<double*>(&x);
*ptr = 3.14;  // UNDEFINED BEHAVIOR! Writing double to int memory

When to use reinterpret_cast:

  • Converting function pointers
  • Working with low-level memory layouts
  • Serialization/deserialization
  • Only when you absolutely know what you're doing!

The compiler performs no checks when using reinterpret_cast. However, sometimes you know that you can cast, say a buffer of bytes, to a specific type. This is when you use reinterpret_cast.

dynamic_cast - Runtime Type Checking

dynamic_cast is used for safe downcasting in inheritance hierarchies. We'll discuss it more in the section where we introduce inheritance. But it is a runtime cast that ensures that types are convertible to each other and throws an error if they are not. Key features:

  • Returns nullptr if the cast fails (for pointers)
  • Throws std::bad_cast if the cast fails (for references)
  • Requires RTTI (Runtime Type Information)
  • Only works with polymorphic types (classes with virtual functions)

Think of it like: dynamic_cast is like asking "Are you really what I think you are?" and getting a safe answer, rather than just assuming.

bit_cast (C++20) - Type-Preserving Cast

bit_cast is a new C++20 feature that reinterprets the bit pattern of one type as another:

cpp
#include <bit>

int x = 42;
float f = std::bit_cast<float>(x);  // Reinterprets int bits as float
std::cout << f << std::endl; // prints 5.88545e-44 (literally reinterprets the bit pattern of x as a float)

When to use bit_cast:

  • Converting between types with the same size
  • Working with binary data
  • Avoiding undefined behavior of reinterpret_cast

This is different from reinterpret_cast. reinterpret_cast is a cast that allows you to cast between completely unrelated types. bit_cast is a cast that allows you to cast between types with the same size.

Summary

You've learned:

  • The difference between implicit and explicit conversions
  • When to use each C++ cast operator
  • The dangers of unsafe casting
  • Best practices for type conversions
  • Common pitfalls and how to avoid them

Remember: Use the right cast for the job, and when in doubt, prefer static_cast!

Questions

Q: What is the main difference between C-style casts and C++ casts?

C++ casts (static_cast, const_cast, reinterpret_cast, dynamic_cast) are more explicit and type-safe. They make the programmer's intent clear and catch more errors at compile time, while C-style casts can hide dangerous conversions.

Q: When should you use static_cast?

A: For well-defined conversions between related types

static_cast is used for well-defined conversions between related types, such as numeric conversions, upcasting in inheritance, and converting void* to other pointer types. It's the safest and most commonly used cast.

Q: What is the danger of using reinterpret_cast?

reinterpret_cast can lead to undefined behavior because it allows converting between completely unrelated types, potentially creating invalid pointers or violating type safety. It should be used very carefully.

Q: What does const_cast do?

A: Removes or adds const qualifier

const_cast removes or adds the const qualifier. However, modifying an originally const object through const_cast results in undefined behavior. It's mainly used for calling non-const functions on const objects.

Q: When is dynamic_cast used?

A: For converting between unrelated pointer types

dynamic_cast is used for safe downcasting in inheritance hierarchies. It performs runtime type checking and returns nullptr if the conversion is invalid, making it safer than static_cast for polymorphic types.