# C++ Casts ## Basic concepts ### What's scalar types? - A scalar type is a basic data type that represents a single value (not composed of other values). It contrasts with more complex types like arrays, structs, or objects. - Typically incl: - Int - Float - Double - Char - Bool ### What's type casting? - Type casting is the process of converting a value from one data type to another — like turning a float into an int. - It’s called “casting” because you're telling the compiler: > 🧙 “Hey, treat this value as this type, not what it originally was.” - Two types of casting - Implicity casting (auto) - The compiler does this for you when it's safe ```c++ int x = 5; double y = x; ``` x is auto converted to 5.0 - Explicit casting (manual) - Explicitly telling the compiler to cast pi from double to int ```c++ double pi = 3.14; int truncated = (int)pi; ``` - Danger of casting - Can lose data: eg. from float to int - Can cause undefined behavior: casting incompatible pointers/ references ### What's ```static_cast``` vs ```dynamic_cast```? #### static_cast: compile time conversion ✅ What it does: - Performs explicit, safe conversions known at compile-time. 🔧 Use cases: - Convert between numeric types (e.g., int to float) - Convert a pointer or reference in a class hierarchy (e.g., Derived* to Base*) - Convert void* to another pointer type (if you're sure it’s safe) 🔐 Safety: - Checked at compile time - No runtime checks — if you do something unsafe (like downcasting to the wrong type), it will compile, but could lead to undefined behavior Example: ```c++ class Animal {}; class Dog : public Animal {}; Dog* d = new Dog(); Animal* a = static_cast<Animal*>(d); // ✅ Upcasting: safe ``` #### dynamic_cast: runtime conversion ✅ What it does: - Used for safe downcasting in an inheritance hierarchy — from a base class to a derived class. 🔧 Use cases: - Cast Base* to Derived* only if the object is actually of Derived type - Requires at least one virtual function in the base class (i.e., RTTI enabled) 🔐 Safety: - Checks at runtime whether the cast is valid - If the object isn’t of the correct derived type: - For pointers → returns ```nullptr``` - For references → throws ```std::bad_cast``` Example: ```c++ class Animal { public: virtual ~Animal() {} // Required for dynamic_cast to work }; class Dog : public Animal {}; class Cat : public Animal {}; Animal* a = new Dog(); Dog* d = dynamic_cast<Dog*>(a); // ✅ succeeds Cat* c = dynamic_cast<Cat*>(a); // ❌ returns nullptr ``` #### When to use static or dynamic cast - Use static_cast when - You know the conversion is safe - Want speed and simplicity - Use dynamic_cast when - You are downcasting and need runtime safety - Not sure of the actual type ### What's ```reinterpret_cast``` and ```const_cast``` #### reinterpret_cast 🔄 What is reinterpret_cast? - reinterpret_cast is a C++ casting operator that performs a low-level, unsafe conversion between unrelated types, typically between: - Different pointer types - Pointers and integers - References of unrelated types - Unlike static_cast, which checks types at compile time and is relatively safe, reinterpret_cast says: >"Treat the raw bits of this object as if they were something else." 🧠 What it does under the hood: - It does not convert values - It reinterprets the binary representation of a value or pointer - So the bit pattern stays the same, but the type changes ✅ When to use it? Common safe use cases: - Pointer <-> Integer conversions (e.g., uintptr_t) - Casting between two pointer types when you know it’s valid (e.g., void* to char*) - Low-level systems or serialization code ⚠️ When to avoid it? - Casting between unrelated classes or types that don’t share memory layout — leads to undefined behavior - To bypass type system protections — it’s dangerous and discouraged unless necessary 🔁 Examples (Conceptual) - Pointer to int ```c++ Data* ptr = new Data(); uintptr_t raw = reinterpret_cast<uintptr_t>(ptr); // serialize ``` Give the numeric memory address of this ptr - Int back to pointer ```c++ Data* ptr2 = reinterpret_cast<Data*>(raw); //deserialize ``` > Take this int and treat it like a ptr to Data #### const_cast 🔹 What is const_cast? - const_cast is used to add or remove const (or volatile) qualifiers from a variable's type. - You typically use it to: - Remove const so that a value can be modified (⚠️ risky!) - Call a function that expects a non-const pointer/reference, but you only have a const one 🧠 What it does: - It tells the compiler: > “I promise I know what I’m doing — please treat this const value as if it were non-const.” - But if the original value really is const (i.e., stored in read-only memory), modifying it causes undefined behavior. ✅ Common Use Case (Safe) Imagine you have a function that doesn’t modify its argument, but isn’t marked const: ```c++ void legacyPrint(char *str); const char* msg = "Hello"; legacyPrint(const_cast<char*>(msg)); ``` Here, you're calling a function that expects char*, but you only have a const char*. As long as you don’t modify the data inside the function, it's safe. ❌ Dangerous Use Case (undefined behavior) ```c++ const int x = 42; int* ptr = const_cast<int*>(&x); *ptr = 99; // ❌ undefined behavior if x is truly const ``` You're trying to change a value marked const. Even though the compiler lets you do it, this is not allowed and may crash or behave unpredictably. 🧠 Rule of Thumb - **Only use const_cast when you are absolutely sure the object wasn’t originally const**. - It’s meant to help interface with poorly designed APIs — not to override language safety. #### Summary of all four casting operators | Cast | Purpose | Runtime Checked? | Typical Use Case | | ------------------ | --------------------------------------------- | ---------------- | ----------------------------------- | | `static_cast` | Compile-time safe conversions | ❌ No | Upcasting, scalar conversions | | `dynamic_cast` | Safe downcasting in class hierarchies | ✅ Yes | Polymorphic downcasting | | `const_cast` | Add or remove `const` / `volatile` qualifiers | ❌ No | Remove `const` from pointers | | `reinterpret_cast` | Low-level, unsafe reinterpretation of bits | ❌ No | Raw memory tricks, casting pointers | ### What's serialization Normally, serialization means converting an object into a format that can be stored/transmitted and then reconstructed. In ex01, it's used more literally and simply: take a pointer to a struct, convert it into a raw integer, then back into the original pointer type. It's more about **low-level representation than saving data**. ### The ```uintptr_t``` type Unsigned int type from <cstdint> that is guaranteed to be able to hold a pointer. This makes it safe to convert pointers to int and back without losing info. ## Ex00 Conversion of scalar types ### Project goal Convert an input string, into char, int, float, and double ### Process 1. Parse the input 2. Try converting the input to each type (char, int, float, double) 3. Handle edge cases (nan, +inf, non displayable chars etc.) 4. Print the result for all four types, handling impossible or invalid conversions ### Relevant functions & techniques #### Conversion functions - ```std::stoi()``` - string to int - ```std::stof()``` - string to float - ```std::stod()``` - string to double #### Exception handling ```c++ try { int i = std::stoi(input); } catch (std::exception& e) { // handle conversion error } ``` #### Type casting To convert between types ```c++ float f = static_cast<float>(int); int i = static_cast<int>(char); ``` #### Character checks To verify if a ```char``` is displayable - ```std::isprint(char)``` - check if a char is printable #### Detect special values Manually handle special float/double values like: - nan, nanf - +inf, -inf, +inff, -inff Need to use: ```std::isnan()```, ```std::isinf()``` in the cmath lib #### String manipulation To detect if a string ends with "f" for float literals - ```str.back() == 'f'``` - ```str.find('.')``` to check for decimal point ## Ex01 Serialization ### Project goal Implement a basic serializer/ deserializer that converts a pointer to an int, then converts it back. Key focuses: - Pointer manipulation - Type casting (reinterpret_cast) - Data representation in memory ## Ex02 Identify real type ### Project goal Identify the actual derived type of an object through a base class pointer or reference at runtime. Key focuses: - Inheritance - Virtual functions - dynamic_cast - Runtime type identification ### Setup conceptually You have: - A base class called Base - Three derived classes: A, B, and C, all inherit from Base Now imagine: - You randomly create an object of type A, B, or C - You only store a pointer or reference to Base - Your job is to identify the real type of that object at runtime ### Key function: ```dynamic_cast``` - Work only with polymorphic classes - Allow to downcast from Base* to Derived* - Return nullptr if the cast is invalid (when using ptr) - Throw an exception if it fails on a reference