CodeNebula
C++98corebeginner

Type Casting

C++98 · The Foundation · Compiler flag: -std=c++98

Explanation

Converts between types using `static_cast`, `dynamic_cast`, etc.

Real-World Scenario

Convert a base class pointer to a derived class in a game.

Quick Snippet

snippet.cpp
Base* b; Derived* d = dynamic_cast<Derived*>(b);

Full Example

type_casting.cpp
// CodeNebula - C++98 Example: Type Casting
// Compile: g++ -std=c++98 -Wall -Wextra -o example type_casting.cpp

// C++98 provides four named cast operators that replace C-style casts with
// explicit, safer alternatives:
//   static_cast    - Compile-time checked conversions (most common)
//   dynamic_cast   - Runtime-checked downcasts in polymorphic hierarchies
//   const_cast     - Add or remove const/volatile qualifiers
//   reinterpret_cast - Low-level bit reinterpretation (dangerous)

#include <iostream>
#include <string>

// ------------------------------------------------------------------
// Class hierarchy for dynamic_cast demonstration
// ------------------------------------------------------------------
class Animal {
public:
    virtual void speak() const {
        std::cout << "  Animal speaks" << std::endl;
    }
    virtual ~Animal() {}
};

class Dog : public Animal {
public:
    void speak() const {
        std::cout << "  Dog: Woof!" << std::endl;
    }
    void fetch() const {
        std::cout << "  Dog fetches the ball!" << std::endl;
    }
};

class Cat : public Animal {
public:
    void speak() const {
        std::cout << "  Cat: Meow!" << std::endl;
    }
    void purr() const {
        std::cout << "  Cat purrs..." << std::endl;
    }
};

// ------------------------------------------------------------------
// Function that takes a const reference (for const_cast demo)
// ------------------------------------------------------------------
void legacyModify(int* p) {
    // Imagine this is a legacy C function that doesn't use const
    // but promises not to modify the value (or we know it's safe)
    std::cout << "  legacyModify reads value: " << *p << std::endl;
}

int main() {
    std::cout << "=== C++98 Type Casting ===" << std::endl;

    // =================================================================
    // 1. static_cast - compile-time conversions
    // =================================================================
    std::cout << "\n--- 1. static_cast ---" << std::endl;

    // a) Numeric type conversions
    int i = 65;
    double d = static_cast<double>(i);
    std::cout << "int 65 -> double: " << d << std::endl;
    // Expected: 65

    double pi = 3.14159;
    int truncated = static_cast<int>(pi);
    std::cout << "double 3.14159 -> int: " << truncated << std::endl;
    // Expected: 3

    // b) int to char (ASCII)
    char ch = static_cast<char>(i);
    std::cout << "int 65 -> char: '" << ch << "'" << std::endl;
    // Expected: 'A'

    // c) Enum to int and back
    enum Color { RED, GREEN, BLUE };
    Color c = GREEN;
    int colorVal = static_cast<int>(c);
    std::cout << "GREEN -> int: " << colorVal << std::endl;
    // Expected: 1

    Color backToColor = static_cast<Color>(2);
    std::cout << "int 2 -> Color: " << backToColor << std::endl;
    // Expected: 2 (BLUE)

    // d) Upcast (derived to base) - always safe
    Dog dog;
    Animal* animalPtr = static_cast<Animal*>(&dog);
    animalPtr->speak();
    // Expected: Dog: Woof!

    // e) Downcast with static_cast (unchecked - use only when sure)
    Dog* dogPtr = static_cast<Dog*>(animalPtr);
    dogPtr->fetch();
    // Expected: Dog fetches the ball!

    // f) void* conversions
    int val = 777;
    void* vp = static_cast<void*>(&val);
    int* ip = static_cast<int*>(vp);
    std::cout << "void* round-trip: " << *ip << std::endl;
    // Expected: 777

    // =================================================================
    // 2. dynamic_cast - runtime-checked polymorphic casts
    // =================================================================
    std::cout << "\n--- 2. dynamic_cast ---" << std::endl;

    // Create animals
    Animal* animals[3];
    animals[0] = new Dog();
    animals[1] = new Cat();
    animals[2] = new Dog();

    for (int idx = 0; idx < 3; ++idx) {
        std::cout << "animals[" << idx << "]: ";

        // Try to cast to Dog*
        Dog* dptr = dynamic_cast<Dog*>(animals[idx]);
        if (dptr != 0) {
            std::cout << "is a Dog -> ";
            dptr->fetch();
        }

        // Try to cast to Cat*
        Cat* cptr = dynamic_cast<Cat*>(animals[idx]);
        if (cptr != 0) {
            std::cout << "is a Cat -> ";
            cptr->purr();
        }
    }
    // Expected:
    // animals[0]: is a Dog ->   Dog fetches the ball!
    // animals[1]: is a Cat ->   Cat purrs...
    // animals[2]: is a Dog ->   Dog fetches the ball!

    // dynamic_cast with references (throws std::bad_cast on failure)
    std::cout << "\ndynamic_cast with references:" << std::endl;
    try {
        Animal& ref = *animals[0];
        Dog& dogRef = dynamic_cast<Dog&>(ref);
        dogRef.speak();
        // Expected: Dog: Woof!
    }
    catch (const std::bad_cast& e) {
        std::cout << "bad_cast: " << e.what() << std::endl;
    }

    // Cleanup
    for (int idx = 0; idx < 3; ++idx) {
        delete animals[idx];
    }

    // =================================================================
    // 3. const_cast - add/remove const
    // =================================================================
    std::cout << "\n--- 3. const_cast ---" << std::endl;

    // a) Remove const to pass to a legacy function
    const int constVal = 42;
    int* mutablePtr = const_cast<int*>(&constVal);
    legacyModify(mutablePtr);
    // Expected: legacyModify reads value: 42
    // WARNING: Actually modifying constVal through mutablePtr is undefined behavior!

    // b) A practical use: non-const overload calling const overload
    // (common pattern to avoid code duplication)
    const std::string greeting = "Hello";
    std::string& mutableRef = const_cast<std::string&>(greeting);
    std::cout << "const_cast string: " << mutableRef << std::endl;
    // Expected: Hello

    // c) Adding const (always safe)
    int mutableVal = 100;
    const int* constPtr = const_cast<const int*>(&mutableVal);
    std::cout << "Added const: " << *constPtr << std::endl;
    // Expected: 100

    // =================================================================
    // 4. reinterpret_cast - low-level bit reinterpretation
    // =================================================================
    std::cout << "\n--- 4. reinterpret_cast ---" << std::endl;

    // a) Pointer to integer and back
    int original = 12345;
    int* origPtr = &original;

    // Store pointer value as an integer
    long ptrAsLong = reinterpret_cast<long>(origPtr);
    std::cout << "Pointer as long: " << ptrAsLong << std::endl;

    // Convert back to pointer
    int* recoveredPtr = reinterpret_cast<int*>(ptrAsLong);
    std::cout << "Recovered value: " << *recoveredPtr << std::endl;
    // Expected: 12345

    // b) View bytes of an integer
    int number = 0x41424344;  // ASCII: 'A' 'B' 'C' 'D'
    char* bytes = reinterpret_cast<char*>(&number);
    std::cout << "Bytes of 0x41424344: ";
    for (int b = 0; b < static_cast<int>(sizeof(int)); ++b) {
        std::cout << "0x" << std::hex
                  << (static_cast<int>(bytes[b]) & 0xFF) << " ";
    }
    std::cout << std::dec << std::endl;
    // Output depends on endianness

    // c) Function pointer cast (rare, platform-specific)
    typedef void (*FuncPtr)();
    FuncPtr fp = reinterpret_cast<FuncPtr>(origPtr);
    // We would never call fp - this just shows the cast compiles
    std::cout << "Function pointer cast compiled (not called)." << std::endl;

    // =================================================================
    // 5. Comparison with C-style cast
    // =================================================================
    std::cout << "\n--- 5. C-style vs C++ casts ---" << std::endl;

    double x = 9.99;

    // C-style cast (works but unclear intent)
    int c_style = (int)x;
    std::cout << "C-style (int)9.99 = " << c_style << std::endl;

    // C++ named cast (clear intent, searchable, compiler-checked)
    int cpp_style = static_cast<int>(x);
    std::cout << "static_cast<int>(9.99) = " << cpp_style << std::endl;
    // Both produce: 9

    std::cout << "\nPrefer named casts: they make intent clear and are searchable." << std::endl;

    return 0;
}

/*
Expected Output:
=== C++98 Type Casting ===

--- 1. static_cast ---
int 65 -> double: 65
double 3.14159 -> int: 3
int 65 -> char: 'A'
GREEN -> int: 1
int 2 -> Color: 2
  Dog: Woof!
  Dog fetches the ball!
void* round-trip: 777

--- 2. dynamic_cast ---
animals[0]: is a Dog ->   Dog fetches the ball!
animals[1]: is a Cat ->   Cat purrs...
animals[2]: is a Dog ->   Dog fetches the ball!

dynamic_cast with references:
  Dog: Woof!

--- 3. const_cast ---
  legacyModify reads value: 42
const_cast string: Hello
Added const: 100

--- 4. reinterpret_cast ---
Pointer as long: <address>
Recovered value: 12345
Bytes of 0x41424344: <depends on endianness>
Function pointer cast compiled (not called).

--- 5. C-style vs C++ casts ---
C-style (int)9.99 = 9
static_cast<int>(9.99) = 9

Prefer named casts: they make intent clear and are searchable.
*/
Run command
g++ -std=c++98 -Wall -Wextra -o example type_casting.cpp