Skip to content

Module 06: C++ Casts

Download Official Subject PDF

Key Concepts:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • Type identification

// C-style casts do everything - too powerful, no clarity
int x = (int)3.14; // OK
int* p = (int*)&x; // Dangerous
const int* cp = &x;
int* mp = (int*)cp; // Removes const - dangerous!
  • 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

Compile-time checked conversions between related types.

1. Numeric conversions

double d = 3.14;
int i = static_cast<int>(d); // 3
float f = static_cast<float>(i); // 3.0f

2. Enum to int (and vice versa)

enum Color { RED, GREEN, BLUE };
Color c = static_cast<Color>(1); // GREEN
int n = static_cast<int>(c); // 1

3. 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);
// Unrelated types - won't compile
double* dp;
int* ip = static_cast<int*>(dp); // ERROR
// const removal - use const_cast
const int* cp;
int* p = static_cast<int*>(cp); // ERROR

Runtime-checked downcasts in polymorphic class hierarchies.

  • Base class must have at least one virtual function
  • Works with pointers and references
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
}
Base& br = derived_object;
try {
Derived& dr = dynamic_cast<Derived&>(br);
// Cast succeeded
}
catch (std::bad_cast& e) {
// Cast failed
}
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
}

Add or remove const/volatile qualifiers.

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);
// Original was non-const, temporarily made const
int x = 42;
const int* cp = &x;
int* p = const_cast<int*>(cp); // Safe - x was never const
*p = 100; // OK
const int CONSTANT = 42;
int* p = const_cast<int*>(&CONSTANT);
*p = 100; // UNDEFINED BEHAVIOR! CONSTANT is truly const

Low-level reinterpretation of bit patterns. Most dangerous cast.

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 d
  • Highly platform-dependent
  • Can violate aliasing rules
  • Only use when you REALLY know what you’re doing

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"
}
#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 errors
if (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 integers
long intValue = std::strtol(str, &endptr, 10); // base 10
#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 infinity
if (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;
}
#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 notation
std::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"
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;
}
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;
};
// Implementation
uintptr_t Serializer::serialize(Data* ptr) {
return reinterpret_cast<uintptr_t>(ptr);
}
Data* Serializer::deserialize(uintptr_t raw) {
return reinterpret_cast<Data*>(raw);
}
// Test
Data original = {42, "test", 3.14f};
uintptr_t serialized = Serializer::serialize(&original);
Data* deserialized = Serializer::deserialize(serialized);
// deserialized should == &original
class Base {
public:
virtual ~Base() {}
};
class A : public Base { };
class B : public Base { };
class C : public Base { };
// Generate random A, B, or C
Base* generate() {
srand(time(NULL));
switch (rand() % 3) {
case 0: return new A();
case 1: return new B();
case 2: return new C();
}
return NULL;
}
#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 numbers
int random = std::rand(); // 0 to RAND_MAX
int random0to9 = std::rand() % 10; // 0 to 9
int random1to6 = std::rand() % 6 + 1; // 1 to 6 (dice roll)
// For RobotomyRequestForm - 50% success rate
bool 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 (...) {}
}

SituationCast to Use
Numeric conversion (int <-> double)static_cast
Upcasting (Derived* -> Base*)static_cast
Downcasting (Base* -> Derived*) with virtualdynamic_cast
Remove/add constconst_cast
Pointer <-> integerreinterpret_cast
Unrelated pointer typesreinterpret_cast
Type-punning (reading bytes)reinterpret_cast

// static_cast - compile-time, related types
int i = static_cast<int>(3.14);
Derived* d = static_cast<Derived*>(base_ptr); // Unchecked!
// dynamic_cast - runtime, polymorphic downcast
Derived* d = dynamic_cast<Derived*>(base_ptr); // NULL if fails
Derived& r = dynamic_cast<Derived&>(base_ref); // throws if fails
// const_cast - add/remove const
int* p = const_cast<int*>(const_ptr);
// reinterpret_cast - bit-level reinterpretation
uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);