Module 06: C++ Casts
Key Concepts:
- static_cast
- dynamic_cast
- const_cast
- reinterpret_cast
- Type identification
1. Why C++ Casts?
Section titled “1. Why C++ Casts?”C-Style Cast Problems
Section titled “C-Style Cast Problems”// C-style casts do everything - too powerful, no clarityint x = (int)3.14; // OKint* p = (int*)&x; // Dangerousconst int* cp = &x;int* mp = (int*)cp; // Removes const - dangerous!C++ Cast Benefits
Section titled “C++ Cast Benefits”- Explicit intent: Clear what type of conversion
- Searchable: Easy to grep for casts
- Type-safe: Compile-time/runtime checks
- Restrictive: Each cast only does specific conversions
2. static_cast
Section titled “2. static_cast”Purpose
Section titled “Purpose”Compile-time checked conversions between related types.
Use Cases
Section titled “Use Cases”1. Numeric conversions
double d = 3.14;int i = static_cast<int>(d); // 3float f = static_cast<float>(i); // 3.0f2. Enum to int (and vice versa)
enum Color { RED, GREEN, BLUE };Color c = static_cast<Color>(1); // GREENint n = static_cast<int>(c); // 13. Pointer up/downcasts (without polymorphism)
class Base { };class Derived : public Base { };
Derived d;Base* bp = static_cast<Base*>(&d); // Upcast (safe)Derived* dp = static_cast<Derived*>(bp); // Downcast (unchecked!)4. void conversions*
int x = 42;void* vp = static_cast<void*>(&x);int* ip = static_cast<int*>(vp);When NOT to Use
Section titled “When NOT to Use”// Unrelated types - won't compiledouble* dp;int* ip = static_cast<int*>(dp); // ERROR
// const removal - use const_castconst int* cp;int* p = static_cast<int*>(cp); // ERROR3. dynamic_cast
Section titled “3. dynamic_cast”Purpose
Section titled “Purpose”Runtime-checked downcasts in polymorphic class hierarchies.
Requirements
Section titled “Requirements”- Base class must have at least one virtual function
- Works with pointers and references
Pointer Syntax
Section titled “Pointer Syntax”class Base {public: virtual ~Base() {}};class Derived : public Base { };
Base* bp = new Derived();Derived* dp = dynamic_cast<Derived*>(bp);
if (dp != NULL) { // Cast succeeded - bp actually pointed to Derived}else { // Cast failed - bp pointed to Base or other type}Reference Syntax
Section titled “Reference Syntax”Base& br = derived_object;try { Derived& dr = dynamic_cast<Derived&>(br); // Cast succeeded}catch (std::bad_cast& e) { // Cast failed}Why Runtime Check?
Section titled “Why Runtime Check?”class Animal { public: virtual ~Animal() {} };class Dog : public Animal { };class Cat : public Animal { };
Animal* animal = getAnimalFromUser(); // Could be Dog or Cat
Dog* dog = dynamic_cast<Dog*>(animal);if (dog) { dog->bark(); // Safe - we know it's really a Dog}4. const_cast
Section titled “4. const_cast”Purpose
Section titled “Purpose”Add or remove const/volatile qualifiers.
Use Cases
Section titled “Use Cases”Remove const (use carefully!)
void legacyFunction(char* str); // Can't change this old function
const char* message = "Hello";legacyFunction(const_cast<char*>(message)); // Remove const
// WARNING: Modifying truly const data is UNDEFINED BEHAVIOR!Add const
int x = 42;const int* cp = const_cast<const int*>(&x);When It’s Safe
Section titled “When It’s Safe”// Original was non-const, temporarily made constint x = 42;const int* cp = &x;int* p = const_cast<int*>(cp); // Safe - x was never const*p = 100; // OKWhen It’s DANGEROUS
Section titled “When It’s DANGEROUS”const int CONSTANT = 42;int* p = const_cast<int*>(&CONSTANT);*p = 100; // UNDEFINED BEHAVIOR! CONSTANT is truly const5. reinterpret_cast
Section titled “5. reinterpret_cast”Purpose
Section titled “Purpose”Low-level reinterpretation of bit patterns. Most dangerous cast.
Use Cases
Section titled “Use Cases”Pointer to integer (and back)
int x = 42;uintptr_t address = reinterpret_cast<uintptr_t>(&x);int* p = reinterpret_cast<int*>(address);Pointer to different type
struct Data { int x; float y; };Data d = {42, 3.14f};char* bytes = reinterpret_cast<char*>(&d);// Now can access raw bytes of dWarnings
Section titled “Warnings”- Highly platform-dependent
- Can violate aliasing rules
- Only use when you REALLY know what you’re doing
6. Module 06 Exercises
Section titled “6. Module 06 Exercises”ex00: Scalar Type Converter
Section titled “ex00: Scalar Type Converter”class ScalarConverter {private: ScalarConverter(); // Not instantiable
public: static void convert(const std::string& literal);};
// Detect type and convert to all scalar types// Input: "42", "42.0f", "42.0", "'*'", "nan", etc.// Output: char, int, float, double representations
void ScalarConverter::convert(const std::string& literal) { // 1. Detect input type (char, int, float, double) // 2. Parse the value // 3. Convert and display all four types
// Handle special cases: nan, inf, impossible conversions}Type Detection Logic:
bool isChar(const std::string& s) { return s.length() == 1 && !isdigit(s[0]);}
bool isInt(const std::string& s) { // Check if all digits (with optional leading sign)}
bool isFloat(const std::string& s) { // Check for 'f' suffix, decimal point // Handle "nanf", "-inff", "+inff"}
bool isDouble(const std::string& s) { // Has decimal point, no 'f' suffix // Handle "nan", "-inf", "+inf"}String to Number Conversion: strtod
Section titled “String to Number Conversion: strtod”#include <cstdlib> // for strtod, strtol
// strtod - string to double (safer than atof)const char* str = "3.14159";char* endptr;double value = std::strtod(str, &endptr);
// Check for conversion errorsif (endptr == str) { // No conversion performed std::cout << "Invalid number" << std::endl;}else if (*endptr != '\0') { // Partial conversion (trailing characters) std::cout << "Trailing chars: " << endptr << std::endl;}
// Also useful: strtol for integerslong intValue = std::strtol(str, &endptr, 10); // base 10Special Value Detection: isnan, isinf
Section titled “Special Value Detection: isnan, isinf”#include <cmath> // for isnan, isinf
double value = std::strtod(str, NULL);
// Check for NaN (Not a Number)if (std::isnan(value)) { std::cout << "Value is nan" << std::endl;}
// Check for infinityif (std::isinf(value)) { if (value > 0) std::cout << "Value is +inf" << std::endl; else std::cout << "Value is -inf" << std::endl;}Character Classification: isprint, isdigit
Section titled “Character Classification: isprint, isdigit”#include <cctype>
char c = '*';
// isprint - is it a printable character?if (std::isprint(c)) { std::cout << "char: '" << c << "'" << std::endl;}else { std::cout << "char: Non displayable" << std::endl;}
// isdigit - is it a digit?if (std::isdigit(c)) { std::cout << c << " is a digit" << std::endl;}I/O Manipulators: fixed, setprecision
Section titled “I/O Manipulators: fixed, setprecision”#include <iomanip> // for setprecision#include <iostream>
double d = 42.0;float f = 42.0f;
// Default output might show "42" instead of "42.0"// Use fixed and setprecision to control decimal places
std::cout << std::fixed; // Use fixed-point notationstd::cout << std::setprecision(1); // 1 decimal place
std::cout << "float: " << f << "f" << std::endl; // "42.0f"std::cout << "double: " << d << std::endl; // "42.0"
// For more precision:std::cout << std::setprecision(6);std::cout << 3.14159265358979 << std::endl; // "3.141593"Complete ScalarConverter Output Example
Section titled “Complete ScalarConverter Output Example”void displayConversions(double value) { // char if (std::isnan(value) || std::isinf(value) || value < 0 || value > 127) { std::cout << "char: impossible" << std::endl; } else if (!std::isprint(static_cast<char>(value))) { std::cout << "char: Non displayable" << std::endl; } else { std::cout << "char: '" << static_cast<char>(value) << "'" << std::endl; }
// int if (std::isnan(value) || std::isinf(value) || value < INT_MIN || value > INT_MAX) { std::cout << "int: impossible" << std::endl; } else { std::cout << "int: " << static_cast<int>(value) << std::endl; }
// float std::cout << std::fixed << std::setprecision(1); if (std::isnan(value)) std::cout << "float: nanf" << std::endl; else if (std::isinf(value)) std::cout << "float: " << (value > 0 ? "+inff" : "-inff") << std::endl; else std::cout << "float: " << static_cast<float>(value) << "f" << std::endl;
// double if (std::isnan(value)) std::cout << "double: nan" << std::endl; else if (std::isinf(value)) std::cout << "double: " << (value > 0 ? "+inf" : "-inf") << std::endl; else std::cout << "double: " << value << std::endl;}ex01: Serialization
Section titled “ex01: Serialization”class Serializer {private: Serializer(); // Not instantiable
public: static uintptr_t serialize(Data* ptr); static Data* deserialize(uintptr_t raw);};
struct Data { int id; std::string name; float value;};
// Implementationuintptr_t Serializer::serialize(Data* ptr) { return reinterpret_cast<uintptr_t>(ptr);}
Data* Serializer::deserialize(uintptr_t raw) { return reinterpret_cast<Data*>(raw);}
// TestData original = {42, "test", 3.14f};uintptr_t serialized = Serializer::serialize(&original);Data* deserialized = Serializer::deserialize(serialized);// deserialized should == &originalex02: Type Identification
Section titled “ex02: Type Identification”class Base {public: virtual ~Base() {}};
class A : public Base { };class B : public Base { };class C : public Base { };
// Generate random A, B, or CBase* generate() { srand(time(NULL)); switch (rand() % 3) { case 0: return new A(); case 1: return new B(); case 2: return new C(); } return NULL;}Random Number Generation
Section titled “Random Number Generation”#include <cstdlib> // for srand, rand#include <ctime> // for time
// Seed the random number generator (do once at program start)std::srand(std::time(NULL)); // Use current time as seed
// Generate random numbersint random = std::rand(); // 0 to RAND_MAXint random0to9 = std::rand() % 10; // 0 to 9int random1to6 = std::rand() % 6 + 1; // 1 to 6 (dice roll)
// For RobotomyRequestForm - 50% success ratebool success = (std::rand() % 2 == 0);if (success) { std::cout << _target << " has been robotomized successfully" << std::endl;} else { std::cout << "Robotomy of " << _target << " failed" << std::endl;}
// Identify using pointer (returns NULL on failure)void identify(Base* p) { if (dynamic_cast<A*>(p)) std::cout << "A" << std::endl; else if (dynamic_cast<B*>(p)) std::cout << "B" << std::endl; else if (dynamic_cast<C*>(p)) std::cout << "C" << std::endl;}
// Identify using reference (throws on failure)void identify(Base& p) { try { (void)dynamic_cast<A&>(p); std::cout << "A" << std::endl; return; } catch (...) {}
try { (void)dynamic_cast<B&>(p); std::cout << "B" << std::endl; return; } catch (...) {}
try { (void)dynamic_cast<C&>(p); std::cout << "C" << std::endl; } catch (...) {}}7. Cast Selection Guide
Section titled “7. Cast Selection Guide”| Situation | Cast to Use |
|---|---|
| Numeric conversion (int <-> double) | static_cast |
| Upcasting (Derived* -> Base*) | static_cast |
| Downcasting (Base* -> Derived*) with virtual | dynamic_cast |
| Remove/add const | const_cast |
| Pointer <-> integer | reinterpret_cast |
| Unrelated pointer types | reinterpret_cast |
| Type-punning (reading bytes) | reinterpret_cast |
Quick Reference
Section titled “Quick Reference”// static_cast - compile-time, related typesint i = static_cast<int>(3.14);Derived* d = static_cast<Derived*>(base_ptr); // Unchecked!
// dynamic_cast - runtime, polymorphic downcastDerived* d = dynamic_cast<Derived*>(base_ptr); // NULL if failsDerived& r = dynamic_cast<Derived&>(base_ref); // throws if fails
// const_cast - add/remove constint* p = const_cast<int*>(const_ptr);
// reinterpret_cast - bit-level reinterpretationuintptr_t addr = reinterpret_cast<uintptr_t>(ptr);