0% found this document useful (0 votes)
14 views117 pages

Back To Basics Casting

The document discusses the concept of casting in C++, explaining its necessity in a statically typed language for working with raw memory and navigating inheritance hierarchies. It covers various types of conversions, including automatic, implicit, and explicit type conversions, along with examples demonstrating their usage and potential pitfalls. The author emphasizes the importance of understanding type systems to avoid undefined behavior and improve code safety.

Uploaded by

karenwangxiaoyu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views117 pages

Back To Basics Casting

The document discusses the concept of casting in C++, explaining its necessity in a statically typed language for working with raw memory and navigating inheritance hierarchies. It covers various types of conversions, including automatic, implicit, and explicit type conversions, along with examples demonstrating their usage and potential pitfalls. The author emphasizes the importance of understanding type systems to avoid undefined behavior and improve code safety.

Uploaded by

karenwangxiaoyu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 117

Back to Basics

Casting
O r: how to s ubv ert the ty pe s y s tem

Brian Ruth (he/him) [email protected]


An Introduction
struct region { int size; };

void init_region(char* backing_buffer, size_t buffer_size ) {


if(buffer_size < current_region.size) {
LOG(“Buffer size too small”);
return;
}
//other init code
}
An Introduction
struct region { int size; };

void init_region(char* backing_buffer, size_t buffer_size ) {


if(buffer_size < current_region.size) {
LOG(“Buffer size too small”);
return;
}
//other init code
}
An Introduction
struct region { int size; };

void init_region(char* backing_buffer, size_t buffer_size ) {


if(buffer_size < (size_t)current_region.size) {
LOG(“Buffer size too small”);
return;
}
//other init code
}
An Introduction
struct region {
constexpr int INVALID_SIZE = -1;
int size = INVALID_SIZE;
};

void init_region(char* backing_buffer, size_t buffer_size ) {


if(buffer_size < (size_t)current_region.size) {
LOG(“Buffer size too small”);
return;
}
//other init code
}
An Introduction
struct region {
constexpr int INVALID_SIZE = -1;
int size = INVALID_SIZE;
};

void init_region(char* backing_buffer, size_t buffer_size ) {


if(buffer_size < (size_t)current_region.size) {
LOG(“Buffer size too small”);
return;
}
//other init code
}

Turns INVALID_SIZE into


a very large number
“In all cases, it would be better if the cast - new or
old - could be eliminated”
–Bjarne Stroustrup
The Design and Evolution of C++
Why do we need casts?
C++ is a statically typed language

Casts allow us to...


1. Work with raw memory
2. Navigate inheritance hierarchy
Why do we need casts?
What is a Type?
0x43 0x50 0x50 0x43 0x4F 0x4E 0x21 0x00

Type Value*
int[2] {1129336899, 2182735}
double 4.81336e-308
float[2] {208.314, 3.05866e-39}
size_t 9374776570171459
struct { uint16_t, uint16_t}[2] {{20547, 17232}, {20047, 33}}
char[8] “CPPCON!”

*64-bit MSVC v142


What is a Type?
0x43 0x50 0x50 0x43 0x4F 0x4E 0x21 0x00

Type Value*
int[2] {1129336899, 2182735}
double 4.81336e-308
float[2] {208.314, 3.05866e-39}
size_t 9374776570171459
struct { uint16_t, uint16_t}[2] {{20547, 17232}, {20047, 33}}
char[8] “CPPCON!”

*64-bit MSVC v142


What is a Type?
0x43 0x50 0x50 0x43 0x4F 0x4E 0x21 0x00

Type Value*
int[2] {1129336899, 2182735}
double 4.81336e-308
float[2] {208.314, 3.05866e-39}
size_t 9374776570171459
struct { uint16_t, uint16_t}[2] {{20547, 17232}, {20047, 33}}
char[8] “CPPCON!”

*64-bit MSVC v142


What is a Type?
0x43 0x50 0x50 0x43 0x4F 0x4E 0x21 0x00

Type Value*
int[2] {1129336899, 2182735}
double 4.81336e-308
float[2] {208.314, 3.05866e-39}
size_t 9374776570171459
struct { uint16_t, uint16_t}[2] {{20547, 17232}, {20047, 33}}
char[8] “CPPCON!”

*64-bit MSVC v142


What is a Type?
0x43 0x50 0x50 0x43 0x4F 0x4E 0x21 0x00

Type Value*
int[2] {1129336899, 2182735}
double 4.81336e-308
float[2] {208.314, 3.05866e-39}
size_t 9374776570171459
struct { uint16_t, uint16_t}[2] {{20547, 17232}, {20047, 33}}
char[8] “CPPCON!”

*64-bit MSVC v142


What is a Type?
0x43 0x50 0x50 0x43 0x4F 0x4E 0x21 0x00

Type Value*
int[2] {1129336899, 2182735}
double 4.81336e-308
float[2] {208.314, 3.05866e-39}
size_t 9374776570171459
struct { uint16_t, uint16_t}[2] {{20547, 17232}, {20047, 33}}
char[8] “CPPCON!”

*64-bit MSVC v142


What is a Type?
0x43 0x50 0x50 0x43 0x4F 0x4E 0x21 0x00

Type Value*
int[2] {1129336899, 2182735}
double 4.81336e-308
float[2] {208.314, 3.05866e-39}
size_t 9374776570171459
struct { uint16_t, uint16_t}[2] {{20547, 17232}, {20047, 33}}
char[8] “CPPCON!”

*64-bit MSVC v142


Automatic Type Conversions
Automatic Conversions

Allows the compiler to choose a sequence of operations to convert from


one type to another without explicitly telling it to do so.

*64-bit MSVC v142


Implicit Type Conversion
Assignment Value*
float f = -42.1234; -42.1234016
double d = f; -42.123401641845703
int i = f; -42
size_t s = f; 18446744073709551574
char c = f; Ö
struct Float { { .m = -42.1234}
explicit Float(float f_): m{f_}{};
float m;
};
Float fl = f;

*64-bit MSVC v142


Implicit Type Conversion
Assignment Value*
float f = -42.1234; -42.1234016
double d = f; -42.123401641845703
int i = f; -42
size_t s = f; 18446744073709551574
char c = f; Ö
struct Float { { .m = -42.1234}
explicit Float(float f_): m{f_}{};
float m;
};
Float fl = f;

*64-bit MSVC v142


Implicit Type Conversion
Assignment Value*
float f = -42.1234; -42.1234016
double d = f; -42.123401641845703
int i = f; -42
size_t s = f; 18446744073709551574
char c = f; Ö
struct Float { { .m = -42.1234}
explicit Float(float f_): m{f_}{};
float m;
};
Float fl = f;

*64-bit MSVC v142


Implicit Type Conversion
Assignment Value*
float f = -42.1234; -42.1234016
double d = f; -42.123401641845703
int i = f; -42
size_t s = f; 18446744073709551574
char c = f; Ö
struct Float { { .m = -42.1234}
explicit Float(float f_): m{f_}{};
float m;
};
Float fl = f;

*64-bit MSVC v142


Implicit Type Conversion
Assignment Value*
float f = -42.1234; -42.1234016
double d = f; -42.123401641845703
int i = f; -42
size_t s = f; 18446744073709551574
char c = f; Ö
struct Float { { .m = -42.1234}
explicit Float(float f_): m{f_}{};
float m;
};
Float fl = f;

*64-bit MSVC v142


Implicit Type Conversion
Assignment Value*
float f = -42.1234; -42.1234016
double d = f; -42.123401641845703
int i = f; -42
size_t s = f; 18446744073709551574
char c = f; Ö
struct Float { { .m = -42.1234}
explicit Float(float f_): m{f_}{};
float m;
};
Float fl = f;

*64-bit MSVC v142


Arithmetic Type Conversion
void drawLine(uint8_t start, uint8_t end);

uint8_t x = 10;
uint8_t width = 50;
drawLine(x, x + width);

https://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions
Sign Conversion
struct region { int size; };

void init_region(char* backing_buffer, size_t buffer_size ) {


if(buffer_size < current_region.size) {
LOG(“Buffer size too small”);
return;
}
//other init code
}
User conversion operators
struct SuperInt {
operator int() const {
return mIntRep;
}
int mIntRep;
};

SuperInt si;
int i = si;
Explicit Type Conversions
Explicit Type Conversions
a.k.a Casts
C-style cast

(<type>)var 1. Create a temporary of <type> using var


2. <type> can be any valid type with
qualifiers
3. Overrides the type system by changing
the meaning of the bits in a variable
4. Will fail to compile under some
circumstances (more later)
5. Can be used in constexpr context
(more later)
6. Can cause undefined behavior
7. Participates in operator precedence
(level 3)
https://en.cppreference.com/w/cpp/language/operator_precedence
C-style cast

(<type>)var 1. Create a temporary of <type> using var


2. <type> can be any valid type with
struct A{};
struct B{}; qualifiers
3. Overrides the type system by changing
int main() {
float f = 7.406f; the meaning of the bits in a variable
int i = (int)f; 4. Will fail to compile under some
A* pa = (A*)&f;
B* pb = (B*)pa;
circumstances (more later)
double d = *(double*)(pb); 5. Can be used in constexpr context
return (int)d; (more later)
}
6. Can cause undefined behavior
7. Participates in operator precedence
(level 3)
https://en.cppreference.com/w/cpp/language/operator_precedence
struct tree { bool has_leaves = true;};
struct car {int model_year = 1982; };

void prune(tree* t) { t->has_leaves = false; }


void drive(const car* c ) {
printf("Driving %d\r\n", c->model_year);
}

int main() {
const tree oak;
car mustang;

drive(&mustang); //normal function call


prune((tree*)&oak); //pruning a const tree
drive((car*)&oak); // driving a tree
prune((tree*)&mustang); // pruning a car
drive(&mustang); // driving a car from 1792
float& f = *(float*)&mustang; // turn a car into ref to float
f = mustang.model_year; //implicit conversion from int to float
drive((car*)&f); // driving a float
return 0;
}
struct tree { bool has_leaves = true;};
struct car {int model_year = 1982; };

void prune(tree* t) { t->has_leaves = false; }


void drive(const car* c ) {
printf("Driving %d\r\n", c->model_year);
}

int main() {
const tree oak;
car mustang;

drive(&mustang); //normal function call


prune((tree*)&oak); //pruning a const tree
drive((car*)&oak); // driving a tree
prune((tree*)&mustang); // pruning a car
drive(&mustang); // driving a car from 1792
float& f = *(float*)&mustang; // turn a car into ref to float
f = mustang.model_year; //implicit conversion from int to float
drive((car*)&f); // driving a float
return 0;
}
struct tree { bool has_leaves = true;};
struct car {int model_year = 1982; };

void prune(tree* t) { t->has_leaves = false; }


void drive(const car* c ) {
printf("Driving %d\r\n", c->model_year);
}

int main() {
const tree oak;
car mustang;

drive(&mustang); //normal function call


prune((tree*)&oak); //pruning a const tree
drive((car*)&oak); // driving a tree
prune((tree*)&mustang); // pruning a car
drive(&mustang); // driving a car from 1792
float& f = *(float*)&mustang; // turn a car into ref to float
f = mustang.model_year; //implicit conversion from int to float
drive((car*)&f); // driving a float
return 0;
}
struct tree { bool has_leaves = true;};
struct car {int model_year = 1982; };

void prune(tree* t) { t->has_leaves = false; }


void drive(const car* c ) {
printf("Driving %d\r\n", c->model_year);
}

int main() {
const tree oak;
car mustang;

drive(&mustang); //normal function call


prune((tree*)&oak); //pruning a const tree
drive((car*)&oak); // driving a tree
prune((tree*)&mustang); // pruning a car
drive(&mustang); // driving a car from 1792
float& f = *(float*)&mustang; // turn a car into ref to float
f = mustang.model_year; //implicit conversion from int to float
drive((car*)&f); // driving a float
return 0;
}
struct tree { bool has_leaves = true;};
struct car {int model_year = 1982; };

void prune(tree* t) { t->has_leaves = false; }


void drive(const car* c ) {
printf("Driving %d\r\n", c->model_year);
}

int main() {
const tree oak;
car mustang;

drive(&mustang); //normal function call


prune((tree*)&oak); //pruning a const tree
drive((car*)&oak); // driving a tree
prune((tree*)&mustang); // pruning a car
drive(&mustang); // driving a car from 1792
float& f = *(float*)&mustang; // turn a car into ref to float
f = mustang.model_year; //implicit conversion from int to float
drive((car*)&f); // driving a float
return 0;
}
struct tree { bool has_leaves = true;};
struct car {int model_year = 1982; };

void prune(tree* t) { t->has_leaves = false; }


void drive(const car* c ) {
printf("Driving %d\r\n", c->model_year);
}

int main() {
const tree oak;
car mustang;

drive(&mustang); //normal function call


prune((tree*)&oak); //pruning a const tree
drive((car*)&oak); // driving a tree
prune((tree*)&mustang); // pruning a car
drive(&mustang); // driving a car from 1792
float& f = *(float*)&mustang; // turn a car into ref to float
f = mustang.model_year; //implicit conversion from int to float
drive((car*)&f); // driving a float
return 0;
}
struct tree { bool has_leaves = true;};
struct car {int model_year = 1982; };

void prune(tree* t) { t->has_leaves = false; }


void drive(const car* c ) {
printf("Driving %d\r\n", c->model_year);
}

int main() {
const tree oak;
car mustang;

drive(&mustang); //normal function call


prune((tree*)&oak); //pruning a const tree
drive((car*)&oak); // driving a tree
prune((tree*)&mustang); // pruning a car
drive(&mustang); // driving a car from 1792
float& f = *(float*)&mustang; // turn a car into ref to float
f = mustang.model_year; //implicit conversion from int to float
drive((car*)&f); // driving a float
return 0;
}
struct tree { bool has_leaves = true;};
struct car {int model_year = 1982; };

void prune(tree* t) { t->has_leaves = false; }


void drive(const car* c ) {
printf("Driving %d\r\n", c->model_year);
}

int main() {
const tree oak;
car mustang;

drive(&mustang); //normal function call


prune((tree*)&oak); //pruning a const tree
drive((car*)&oak); // driving a tree
prune((tree*)&mustang); // pruning a car
drive(&mustang); // driving a car from 1792
float& f = *(float*)&mustang; // turn a car into ref to float
f = mustang.model_year; //implicit conversion from int to float
drive((car*)&f); // driving a float
return 0;
}
struct tree { bool has_leaves = true;};
struct car {int model_year = 1982; };

void prune(tree* t) { t->has_leaves = false; }


void drive(const car* c ) {
printf("Driving %d\r\n", c->model_year);
}

int main() {
const tree oak;
car mustang;

drive(&mustang); //normal function call


prune((tree*)&oak); //pruning a const tree
drive((car*)&oak); // driving a tree
prune((tree*)&mustang); // pruning a car
drive(&mustang); // driving a car from 1792
float& f = *(float*)&mustang; // turn a car into ref to float
f = mustang.model_year; //implicit conversion from int to float
drive((car*)&f); // driving a float
return 0;
}
Function pointers are types, so you can cast them
Function pointers are types, so you can cast them

Type Syntax Example


function pointer (<return>(*)(<args...>))<var> (int(*)(int))f
pointer to member function (<return> <class>::*(<args...>))<var> (int (S::*)(int))s_memberptr;
Function pointers are types, so you can cast them

Type Syntax Example


function pointer (<return>(*)(<args...>))<var> (int(*)(int))f
pointer to member function (<return> <class>::*(<args...>))<var> (int (S::*)(int))s_memberptr;

void run_function(void* fptr) {


auto* f = (void(*)(int))fptr;
f(7);
}

void someFunc(int i) {printf("%d\r\n", i);}

int main( ) {
run_function((void*)someFunc);
return 0;
}
C++ Functional Cast (a.k.a Constructor Call Notation)
<type>(var)
struct A{virtual ~A() = default;}; 1. Creates a temporary <type> from var
struct B:public A{};
struct C{}; 2. Provides parity with C++ constructors
for built in types
template <typename T, typename F>
T convert_to(F& f) { return T(f); }
3. Can only use a single word type name
4. Participates in operator precedence
int main() { (level 2)
int i = 7;
float f = convert_to<float>(i);
//A* pa = A*(&f); //<-- Will not compile
using astar = A*;
A* pa = astar(&f);
C* pc = convert_to<C*>(pa);
B& rb = convert_to<B&>(*pa);
return 0;
}
C++ Functional Cast (a.k.a Constructor Call Notation)
<type>(var)
struct A{virtual ~A() = default;}; 1. Creates a temporary <type> from var
struct B:public A{};
struct C{}; 2. Provides parity with C++ constructors
for built in types
template <typename T, typename F>
T convert_to(F& f) { return T(f); }
3. Can only use a single word type name
4. Participates in operator precedence
int main() { (level 2)
int i = 7;
float f = convert_to<float>(i);
//A* pa = A*(&f); //<-- Will not compile
using astar = A*;
A* pa = astar(&f);
C* pc = convert_to<C*>(pa);
B& rb = convert_to<B&>(*pa);
return 0;
}
C++ Functional Cast (a.k.a Constructor Call Notation)
<type>(var)
struct A{virtual ~A() = default;}; 1. Creates a temporary <type> from var
struct B:public A{};
struct C{}; 2. Provides parity with C++ constructors
for built in types
template <typename T, typename F>
T convert_to(F& f) { return T(f); }
3. Can only use a single word type name
4. Participates in operator precedence
int main() { (level 2)
int i = 7;
float f = convert_to<float>(i);
//A* pa = A*(&f); //<-- Will not compile
using astar = A*;
A* pa = astar(&f);
C* pc = convert_to<C*>(pa);
B& rb = convert_to<B&>(*pa);
return 0;
}
C++ Functional Cast (a.k.a Constructor Call Notation)
<type>(var)
struct A{virtual ~A() = default;}; 1. Creates a temporary <type> from var
struct B:public A{};
struct C{}; 2. Provides parity with C++ constructors
for built in types
template <typename T, typename F>
T convert_to(F& f) { return T(f); }
3. Can only use a single word type name
4. Participates in operator precedence
int main() { (level 2)
int i = 7;
float f = convert_to<float>(i);
//A* pa = A*(&f); //<-- Will not compile
using astar = A*;
A* pa = astar(&f);
C* pc = convert_to<C*>(pa);
B& rb = convert_to<B&>(*pa);
return 0;
}
C++ Functional Cast (a.k.a Constructor Call Notation)
<type>(var)
struct A{virtual ~A() = default;}; 1. Creates a temporary <type> from var
struct B:public A{};
struct C{}; 2. Provides parity with C++ constructors
for built in types
template <typename T, typename F>
T convert_to(F& f) { return T(f); }
3. Can only use a single word type name
4. Participates in operator precedence
int main() { (level 2)
int i = 7;
float f = convert_to<float>(i);
//A* pa = A*(&f); //<-- Will not compile
using astar = A*;
A* pa = astar(&f);
C* pc = convert_to<C*>(pa);
B& rb = convert_to<B&>(*pa);
return 0;
}
C++ Functional Cast (a.k.a Constructor Call Notation)
<type>(var)
struct A{virtual ~A() = default;}; 1. Creates a temporary <type> from var
struct B:public A{};
struct C{}; 2. Provides parity with C++ constructors
for built in types
template <typename T, typename F>
T convert_to(F& f) { return T(f); }
3. Can only use a single word type name
4. Participates in operator precedence
int main() { (level 2)
int i = 7;
float f = convert_to<float>(i);
//A* pa = A*(&f); //<-- Will not compile
using astar = A*;
A* pa = astar(&f);
C* pc = convert_to<C*>(pa);
B& rb = convert_to<B&>(*pa);
return 0;
}
C++ Functional Cast (a.k.a Constructor Call Notation)
<type>(var)
struct A{virtual ~A() = default;}; 1. Creates a temporary <type> from var
struct B:public A{};
struct C{}; 2. Provides parity with C++ constructors
for built in types
template <typename T, typename F>
T convert_to(F& f) { return T(f); }
3. Can only use a single word type name
4. Participates in operator precedence
int main() { (level 2)
int i = 7;
float f = convert_to<float>(i);
//A* pa = A*(&f); //<-- Will not compile
using astar = A*;
A* pa = astar(&f);
C* pc = convert_to<C*>(pa);
B& rb = convert_to<B&>(*pa);
return 0;
}
Problems with C-style and Functional notation casts

1. Single notation, multiple meanings


2. Error prone
3. Not grep-able
4. Complicate the C and C++ grammar
Goals for C++ casting

1. Different notation or different tasks


2. Easily recognized and searchable
3. Perform all operations that C casts can
4. Eliminate unintended errors
Goals for C++ casting

1. Different notation or different tasks


2. Easily recognized and searchable
3. Perform all operations that C casts can
4. Eliminate unintended errors
5. Make casting less enticing
C++ casting operators*

1. static_cast
2. const_cast
3. dynamic_cast
4. reinterpret_cast

*keywords
static_cast<T>

struct B {}; 1. Creates a temporary of type from var


struct D : public B {};
2. Tries to find a path from T1 to T2 via
int main() {
int i = 7001;
implicit and user-defined conversion
float f = static_cast<float>(i); // 1 or construction. Cannot remove CV
uint8_t ui8 = static_cast<uint8_t>(1.75f * f); // 2
D d; qualification.
B& rb = d;
D& rd = static_cast<D&>(rb); //3
3. Use when you want to:
}
return 0; 1. Clarify implicit conversion
void* some_c_handler(void* pv) {
2. Indicate intentional truncation
B* pb = static_cast<B*>(pv); // 4 3. Cast between base and derived
}
return pb;
4. Cast between void* and T*
static_cast<T> multiple hops

struct A { explicit A(int){ puts("A");}};


struct E { 1. A has a constructor that takes
operator int(){
puts("B::operator int"); a single int
return 0; 2. E has a user defined
}
}; conversion to int
int main() {
E e;
A a = static_cast<A>(e);
return 0;
}
static_cast<T> multiple hops

struct A { explicit A(int){ puts("A");}};


struct E { 1. A has a constructor that takes
operator int(){
puts("B::operator int"); a single int
return 0; 2. E has a user defined
}
}; conversion to int
int main() {
E e;
A a = static_cast<A>(e);
return 0;
}
static_cast<T> multiple hops

struct A { explicit A(int){ puts("A");}};


struct E { 1. A has a constructor that takes
operator int(){
puts("B::operator int"); a single int
return 0; 2. E has a user defined
}
}; conversion to int
int main() {
E e;
A a = static_cast<A>(e);
return 0;
}
static_cast<T> multiple hops

struct A { explicit A(int){ puts("A");}};


struct E { 1. A has a constructor that takes
operator int(){
puts("B::operator int");
a single int
return 0; 2. E has a user defined
}
}; conversion to int
int main() {
E e;
A a = static_cast<A>(e);
return 0;
}
static_cast<T> multiple hops

struct A { explicit A(int){ puts("A");}};


struct E { 1. A has a constructor that takes
operator int(){
puts("B::operator int");
a single int
return 0; 2. E has a user defined
} conversion to int
};

int main() {
E e;
A a = static_cast<A>(e);
return 0;
}
static_cast<T> multiple hops

struct A { explicit A(int){ puts("A");}};


struct E { 1. A has a constructor that takes
operator int(){
puts("B::operator int"); a single int
return 0; 2. E has a user defined
}
}; conversion to int
int main() {
E e;
A a{static_cast<int>(e)};
return 0;
}
static_cast<T> and inheritance

struct Base1 { virtual ~Base1() = default; int i;};


struct Base2 { virtual ~Base2() = default; int j;};
struct Derived : public Base1, public Base2 { int k; };
void CheckSame(void* p1, void* p2) {
if (p1 == p2) { puts("Same!");}
else { puts("Different!"); }
}

int main() {
Derived d;
Derived* derivedptr = &d;
Base1* base1ptr = static_cast<Base1*>(&d);
CheckSame(derivedptr, base1ptr);

Base2* base2ptr = static_cast<Base2*>(&d);


CheckSame(derivedptr, base2ptr);

return 0;
}
static_cast<T> and inheritance

struct Base1 { virtual ~Base1() = default; int i;};


struct Base2 { virtual ~Base2() = default; int j;};
struct Derived : public Base1, public Base2 { int k; };
void CheckSame(void* p1, void* p2) {
if (p1 == p2) { puts("Same!");}
else { puts("Different!"); }
}

int main() {
Derived d;
Derived* derivedptr = &d;
Base1* base1ptr = static_cast<Base1*>(&d);
CheckSame(derivedptr, base1ptr);

Base2* base2ptr = static_cast<Base2*>(&d);


CheckSame(derivedptr, base2ptr);

return 0;
}
static_cast<T> and inheritance

struct Base1 { virtual ~Base1() = default; int i;};


struct Base2 { virtual ~Base2() = default; int j;};
struct Derived : public Base1, public Base2 { int k; };
void CheckSame(void* p1, void* p2) {
if (p1 == p2) { puts("Same!");}
else { puts("Different!"); }
}

int main() {
Derived d;
Derived* derivedptr = &d;
Base1* base1ptr = static_cast<Base1*>(&d);
CheckSame(derivedptr, base1ptr);

Base2* base2ptr = static_cast<Base2*>(&d);


CheckSame(derivedptr, base2ptr);

return 0;
}
static_cast<T> and inheritance

struct Base1 { virtual ~Base1() = default; int i;};


struct Base2 { virtual ~Base2() = default; int j;};
struct Derived : public Base1, public Base2 { int k; };
void CheckSame(void* p1, void* p2) {
if (p1 == p2) { puts("Same!");}
else { puts("Different!"); }
}

int main() {
Derived d;
Derived* derivedptr = &d;
Base1* base1ptr = static_cast<Base1*>(&d);
CheckSame(derivedptr, base1ptr);

Base2* base2ptr = static_cast<Base2*>(&d);


CheckSame(derivedptr, base2ptr);

return 0;
}
static_cast<T> and inheritance
Derived
• static_cast to one of the derivedptr base1ptr
base types will offset the Base1
pointer into the derived type to int i
the base type's location base2ptr
Base2
int j

int k
static_cast<T> and inheritance
Derived
struct Base1 { virtual ~Base1() = default; int i;};
struct Base2 { virtual ~Base2() = default; int j;};
derivedptr base1ptr
struct Derived : public Base1, public Base2 { int k; }; Base1
void CheckSame(void* p1, void* p2) {
if (p1 == p2) { puts("Same!");}
int i
else { puts("Different!"); } base2ptr
}
Base2
int main() {
Derived d;
int j
Derived* derivedptr = &d;
Base1* base1ptr = static_cast<Base1*>(&d);
CheckSame(derivedptr, base1ptr);
int k
Base2* base2ptr = static_cast<Base2*>(&d);
CheckSame(derivedptr, base2ptr);

void* derived_plus_offset =
(char*)derivedptr + sizeof(Base1);
CheckSame(derived_plus_offset, base2ptr);
return 0;
}
static_cast<T> is not infallible
struct Base { virtual ~Base() = default;
virtual void f() { puts("base");}
};
• static_cast does not protect
struct Derived : public Base {
void f() override { puts("Derived");} against downcasting to an
}; unrelated type
struct other : public Base {
void f() override { puts("other");}
};

int main() {
Derived d;
Base& b = d;
d.f();
b.f();
other& a = static_cast<other&>(b);
a.f();
static_assert(std::is_same<decltype(a), other&>::value,
"not the same");
return 0;
}
static_cast<T> is not infallible
struct Base { virtual ~Base() = default;
virtual void f() { puts("base");}
};
• static_cast does not protect
struct Derived : public Base {
void f() override { puts("Derived");} against downcasting to an
}; unrelated type
struct other : public Base {
void f() override { puts("other");}
};

int main() {
Derived d;
Base& b = d;
d.f();
b.f();
other& a = static_cast<other&>(b);
a.f();
static_assert(std::is_same<decltype(a), other&>::value,
"not the same");
return 0;
}
static_cast<T> is not infallible
struct Base { virtual ~Base() = default;
virtual void f() { puts("base");}
};
• static_cast does not protect
struct Derived : public Base {
void f() override { puts("Derived");} against downcasting to an
}; unrelated type
struct other : public Base {
void f() override { puts("other");}
};

int main() {
Derived d;
Base& b = d;
d.f();
b.f();
other& a = static_cast<other&>(b);
a.f();
static_assert(std::is_same<decltype(a), other&>::value,
"not the same");
return 0;
}
static_cast<T> is not infallible
struct Base { virtual ~Base() = default;
virtual void f() { puts("base");}
};
• static_cast does not protect
struct Derived : public Base {
void f() override { puts("Derived");} against downcasting to an
}; unrelated type
struct other : public Base {
void f() override { puts("other");}
};

int main() {
Derived d;
Base& b = d;
d.f();
b.f();
other& a = static_cast<other&>(b);
a.f();
static_assert(std::is_same<decltype(a), other&>::value,
"not the same");
return 0;
}
const_cast<T>

void use_pointer( int* pi) {


printf("Use %d\r\n", *pi); 1. Removes or adds const or
}
void modify_pointer( int* pi) { *pi = 42; } volatile qualifiers from or to
a variable, cannot change type
int main() {
const int i = 7;
2. Does NOT change the CV
qualification of the original
use_pointer(const_cast<int*>(&i));
modify_pointer(const_cast<int*>(&i));
variable
printf("Modified %d\r\n", i);

int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>

void use_pointer( int* pi) {


printf("Use %d\r\n", *pi); 1. Removes or adds const or
}
void modify_pointer( int* pi) { *pi = 42; } volatile qualifiers from or to
a variable, cannot change type
int main() {
const int i = 7;
2. Does NOT change the CV
qualification of the original
use_pointer(const_cast<int*>(&i));
modify_pointer(const_cast<int*>(&i));
variable
printf("Modified %d\r\n", i);

int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>

void use_pointer( int* pi) {


printf("Use %d\r\n", *pi); 1. Removes or adds const or
}
void modify_pointer( int* pi) { *pi = 42; } volatile qualifiers from or to
a variable, cannot change type
int main() {
const int i = 7;
2. Does NOT change the CV
qualification of the original
use_pointer(const_cast<int*>(&i));
modify_pointer(const_cast<int*>(&i));
variable
printf("Modified %d\r\n", i);

int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>

void use_pointer( int* pi) {


printf("Use %d\r\n", *pi); 1. Removes or adds const or
}
void modify_pointer( int* pi) { *pi = 42; } volatile qualifiers from or to
a variable, cannot change type
int main() {
const int i = 7;
2. Does NOT change the CV
qualification of the original
use_pointer(const_cast<int*>(&i));
modify_pointer(const_cast<int*>(&i));
variable
printf("Modified %d\r\n", i);

int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>

void use_pointer( int* pi) {


printf("Use %d\r\n", *pi); 1. Removes or adds const or
}
void modify_pointer( int* pi) { *pi = 42; } volatile qualifiers from or to
a variable, cannot change type
int main() {
const int i = 7;
2. Does NOT change the CV
qualification of the original
use_pointer(const_cast<int*>(&i));
modify_pointer(const_cast<int*>(&i));
variable
printf("Modified %d\r\n", i);

int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>

void use_pointer( int* pi) {


printf("Use %d\r\n", *pi); 1. Removes or adds const or
}
void modify_pointer( int* pi) { *pi = 42; } volatile qualifiers from or to
a variable, cannot change type
int main() {
const int i = 7;
2. Does NOT change the CV
qualification of the original
use_pointer(const_cast<int*>(&i));
modify_pointer(const_cast<int*>(&i));
variable
printf("Modified %d\r\n", i);

int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T> example: member overload
struct my_array {
const char& operator[] (size_t offset) const {
return buffer[offset]; • Used to prevent code duplication
}
for member functions
private:
char buffer[10];
};

int main() {
const my_array a;
const auto& c = a[4];
return 0;
}
const_cast<T> example: member overload
struct my_array {
const char& operator[] (size_t offset) const {
return buffer[offset]; • Used to prevent code duplication
}
for member functions
private:
char buffer[10];
};

int main() {
const my_array a;
const auto& c = a[4];
my_array mod_a;
mod_a[4] = 7;

return 0;
}
const_cast<T> example: member overload
struct my_array {
const char& operator[] (size_t offset) const {
return buffer[offset]; • Used to prevent code duplication
}
for member functions
char& operator[](size_t offset) {
return buffer[offset];
}

private:
char buffer[10];
};

int main() {
const my_array a;
const auto& c = a[4];
my_array mod_a;
mod_a[4] = 7;

return 0;
}
const_cast<T> example: member overload
class my_array {
public:
char& operator[](size_t offset) { • Used to prevent code duplication
return const_cast<char&>(
const_cast<const my_array&>(*this)[offset]); for member functions
}
const char& operator[] (size_t offset) const {
return buffer[offset];
}
private:
char buffer[10];
};

int main() {
const my_array a;
const auto& c = a[4];
my_array mod_a;
mod_a[4] = 7;
return 0;
}
Run Time Type Information (RTTI)

1. Extra information stored for each


polymorphic type in an implementation
defined struct
2. Allows for querying type information at
run time
3. Can be disabled to save space
(gcc/clang –fno-rtti, msvc /GR-)
dynamic_cast<T>

struct A { virtual ~A() = default; }; 1. See if To is in the same public inheritance


struct B : public A { };
struct C : public A {}; tree as From
int main() {
2. Can only be a reference or pointer
C c; 3. Cannot remove CV
B b;
std::vector<A*> a_list = { &c, &b}; 4. From must be polymorphic
for(size_t i = 0; i < a_list.size(); ++i) {
5. Requires RTTI
A* pa = a_list[i]; 6. Returns nullptr for pointers and throws
if( dynamic_cast<B*>(pa)){
printf("a_list[%lu] was a B\r\n", i); std::bad_cast for references if the types
}
if( dynamic_cast<C*>(pa)) {
are not related
printf("a_list[%lu] was a C\r\n", i);
}
}
return 0;
}
dynamic_cast<T> example: UI Framework
struct Widget {};
struct Label : public Widget {};
struct Button : public Widget { void DoClick(); };

struct Page {
std::vector<Widget> mWidgetList;

template<typename T> T* getWidget(WidgetId id) {


return dynamic_cast<T*>(&mWidgetList[id]);
}

void Page::OnTouch(WidgetId id) {


auto* touchedWidget = getWidget<Button>(id);
if(touchedWidget) {
touchedWidget->DoClick();
}
//more processing
}

Code::Bytes
dynamic_cast<T> example: UI Framework
struct Widget {};
struct Label : public Widget {};
struct Button : public Widget { void DoClick(); };

struct Page {
std::vector<Widget> mWidgetList;

template<typename T> T* getWidget(WidgetId id) {


return dynamic_cast<T*>(&mWidgetList[id]);
}

void Page::OnTouch(WidgetId id) {


auto* touchedWidget = getWidget<Button>(id);
if(touchedWidget) {
touchedWidget->DoClick();
}
//more processing
}

Code::Bytes
dynamic_cast<T> example: UI Framework
struct Widget {};
struct Label : public Widget {};
struct Button : public Widget { void DoClick(); };

struct Page {
std::vector<Widget> mWidgetList;

template<typename T> T* getWidget(WidgetId id) {


return dynamic_cast<T*>(&mWidgetList[id]);
}

void Page::OnTouch(WidgetId id) {


auto* touchedWidget = getWidget<Button>(id);
if(touchedWidget) {
touchedWidget->DoClick();
}
//more processing
}

Code::Bytes
dynamic_cast can be expensive

from gcc’s rtti.c


dynamic_cast can be expensive

from gcc’s rtti.c


reinterpret_cast<T>

struct A{}; 1. Can change any pointer or reference


struct B{ int i; int j;};
int main() type to any other pointer or reference
{
int i = 0;
type
int* pi = &i; 2. Also called type-punning
uintptr_t uipt = reinterpret_cast<uintptr_t>(pi);
float& f = reinterpret_cast<float&>(i); 3. Cannot be used in a constexpr
A a;
B* pb = reinterpret_cast<B*>(&a);
context
char buff[10]; 4. Can NOT remove CV qualification
B* b_buff = reinterpret_cast<B*>(buff);
return 0; 5. Does not ensure sizes of To and From
} are the same
reinterpret_cast<T>

struct A{}; 1. Can change any pointer or reference


struct B{ int i; int j;};
int main() type to any other pointer or reference
{
int i = 0;
type
int* pi = &i; 2. Also called type-punning
uintptr_t uipt = reinterpret_cast<uintptr_t>(pi);
float& f = reinterpret_cast<float&>(i); 3. Cannot be used in a constexpr
A a;
B* pb = reinterpret_cast<B*>(&a);
context
char buff[10]; 4. Can NOT remove CV qualification
B* b_buff = reinterpret_cast<B*>(buff);
volatile int& REGISTER = 5. Does not ensure sizes of To and From
*reinterpret_cast<int*>(0x1234); //6
return 0;
are the same
} 6. Useful for memory mapped
functionality
reinterpret_cast<T> accessing private base

struct B { void m(){ puts("private to D");}};


struct D: private B {};

int main() {
D d;
B& b = reinterpret_cast<B&>(d);
b.m();
return 0;
}
Type Aliasing

The act of using the memory of one type as if it were a different type
when the memory layouts of the two types are compatible

compatible types
struct Point {
incompatible types
int x; float f = 1.0f;
int y; int* i =
}; reinterpret_cast<int*>(&f);
struct Location {
int x;
int y;
};

Point p{1,2};
auto* loc =
reinterpret_cast<Location*>(&p); https://tinyurl.com/32b3cdjp
Type Aliasing

The act of using the memory of one type as if it were a different type
when the memory layouts of the two types are compatible

compatible types
struct Point {
incompatible types
int x; float f = 1.0f;
int y; int* i =
}; reinterpret_cast<int*>(&f);
struct Location {
int x;
int y;
};

Point p{1,2};
auto* loc =
reinterpret_cast<Location*>(&p); https://tinyurl.com/32b3cdjp
Type Aliasing

The act of using the memory of one type as if it were a different type
when the memory layouts of the two types are compatible

compatible types
struct Point {
incompatible types
int x; float f = 1.0f;
int y; int* i =
}; reinterpret_cast<int*>(&f);
struct Location {
int x;
int y;
};

Point p{1,2};
auto* loc =
reinterpret_cast<Location*>(&p); https://tinyurl.com/32b3cdjp
Type Aliasing

The act of using the memory of one type as if it were a different type
when the memory layouts of the two types are compatible

compatible types? it depends...


struct Point { Point p{1,2};
int x; auto* p3d =
int y; reinterpret_cast<Point3D*>(&p);
}; printf("px: %d p3dx: %d", p.x, p3d->x);
struct Point3D {
int x; std::array ap = {Point{1,2}, Point{3,4}};
int y; auto* ap3d =
int z; reinterpret_cast<Point3D*>(ap.data());
}; printf("ap[1]x: %d, ap3d[1]x: %d", ap[1].x,
ap3d[1].x);

https://tinyurl.com/32b3cdjp
Type Aliasing

The act of using the memory of one type as if it were a different type
when the memory layouts of the two types are compatible

compatible types? it depends...


struct Point { Point p{1,2};
int x; auto* p3d =
int y; reinterpret_cast<Point3D*>(&p);
}; printf("px: %d p3dx: %d", p.x, p3d->x);
struct Point3D {
int x; std::array ap = {Point{1,2}, Point{3,4}};
int y; auto* ap3d =
int z; reinterpret_cast<Point3D*>(ap.data());
}; printf("ap[1]x: %d, ap3d[1]x: %d", ap[1].x,
ap3d[1].x);

https://tinyurl.com/32b3cdjp
Type Aliasing

The act of using the memory of one type as if it were a different type
when the memory layouts of the two types are compatible

compatible types? it depends...


struct Point { Point p{1,2};
int x; auto* p3d =
int y; reinterpret_cast<Point3D*>(&p);
}; printf("px: %d p3dx: %d", p.x, p3d->x);
struct Point3D {
int x; std::array ap = {Point{1,2}, Point{3,4}};
int y; auto* ap3d =
int z; reinterpret_cast<Point3D*>(ap.data());
}; printf("ap[1]x: %d, ap3d[1]x: %d", ap[1].x,
ap3d[1].x);

https://tinyurl.com/32b3cdjp
How C-style Casts are Really Performed in C++
YES Select and try
T conv = (T)val; T conv = const_cast<T>(val); Rule Match?
to compile
NO
YES
T conv = static_cast<T>(val); Rule Match?
NO
YES
T conv = const_cast<T>(static_cast<const T>(val)); Rule Match?
NO
YES
T conv = reinterpret_cast<T>(val); Rule Match?
NO
YES
T conv = const_cast<T>(reinterpret_cast<const T>(val)); Rule Match?

NO

error: invalid cast


A selection of additional C++ "casts"

1. bit_cast
2. move/move_if_noexcept
3. forward
4. as_const
5. to_underlying (C++23)
bit_cast<T>

struct A{int x; int y;}; 1. Located in the <bit> header


struct B{ double d; }; 2. Converts From into a bit representation in To
struct C{ int i; };
3. Requires To and From to be the same size
int main() 4. Requires To and From to be trivially copyable
{
A a; 5. Can be used in a constexpr context*
B b = std::bit_cast<B>(a); 6. Fails to compile if cast is invalid
//C c = std::bit_cast<C>(a); //different size
float f = 7.05f;
7. Can introduce UB
uint32_t ui = std::bit_cast<uint32_t>(f); 8. Requires C++20
return 0;
}

*As long as To and From are both not or do not contain: union, pointer, pointer to member, volatile-
qualified type or have a non-static data member of reference type
bit_cast<T>

struct A{int x; int y;}; 1. Located in the <bit> header


If you
struct need d;
B{ double this
}; type of cast in C or pre2. C++20, use From
Converts into a not
memcpy a cast:
bit representation in To
struct C{ int i; };
3. Requires To and From to be the same size
int main()
int 4.main()
Requires To and From to be trivially copyable
{
A a; { 5. Can be used in a constexpr context*
B b = std::bit_cast<B>(a); 6. fFails
float to compile if cast is invalid
= 7.05f;
7. Can introduce
//C c = std::bit_cast<C>(a); //different size uint32_t ui = 0;
float f = 7.05f;
UB
uint32_t ui = std::bit_cast<uint32_t>(f);
memcpy(&ui,
8. &f,
Requires sizeof(f));
C++20
return 0; return 0;
} }

*As long as To and From are both not or do not contain: union, pointer, pointer to member, volatile-
qualified type or have a non-static data member of reference type
std::move & std::move_if_no_except

struct Employee { 1. Converts named variables (lvalues) to


Employee(std::string aName)
: mName(std::move(aName)) {} unnamed variables (rvalues)
private:
2. If present, calls the move constructor of To,
std::string mName; if not, calls copy constructor
};
3. Do not return std::move(var)
int main()
{
4. Equivalent to static_cast<T&&>(var)
std::vector<std::unique_ptr<Employee>> employees; 5. move_if_no_except will trigger the
auto person = std::make_unique<Employee>("me");
employees.emplace_back(std::move(person)); copy constructor if the move constructor of
}
return 0; the destination is not marked noexcept
std::forward

void f(int const &arg) { puts("by lvalue"); } • Keeps the rvalue or lvalued-ness
void f(int && arg) { puts("by rvalue"); }
type passed to a template when
template< typename T >
void func(T&& arg) {
passing to another function
printf(" std::forward: ");
f( std::forward<T>(arg) );
printf(" normal: ");
f(arg);
}

int main() {
puts("call with rvalue:");
func(5);
puts("call with lvalue:");
int x = 5;
func(x);
}
std::forward

void f(int const &arg) { puts("by lvalue"); } • Keeps the rvalue or lvalued-ness
void f(int && arg) { puts("by rvalue"); }
type passed to a template when
template< typename T >
void func(T&& arg) {
passing to another function
printf(" std::forward: ");
f( std::forward<T>(arg) );
printf(" normal: ");
f(arg);
}

int main() {
puts("call with rvalue:");
func(5);
puts("call with lvalue:");
int x = 5;
func(x);
}
std::forward

void f(int const &arg) { puts("by lvalue"); } • Keeps the rvalue or lvalued-ness
void f(int && arg) { puts("by rvalue"); }
type passed to a template when
template< typename T >
void func(T&& arg) {
passing to another function
printf(" std::forward: ");
f( std::forward<T>(arg) );
printf(" normal: ");
f(arg);
}

int main() {
puts("call with rvalue:");
func(5);
puts("call with lvalue:");
int x = 5;
func(x);
}
std::as_const

struct S { 1. Adds const to var


void f() const { puts(“const”); }
void f() { puts(“non-const”); } 2. Less verbose way of doing
}; static_cast<const T&>(var)
int main() {
S s;
s.f();
std::as_const(s).f();
return 0;
}
std::to_underlying

enum struct Result : int16_t { Ok = 1 }; 1. Proposed for C++23


enum Unscoped : int { Fail = -1 }; 2. Converts a sized enum to its value as the
void print(int i) {
underlying type
std::cout << "int: " << i << "\n"; 3. Equivalent to
} static_cast<std::underlying_type_t<T>>(var)
void print(int16_t i) {
std::cout << "int16_t: " << i << "\n";
}

int main() {
auto res = Result::Ok;
print(std::to_underlying(res));
auto unscoped = Fail;
print(std::to_underlying(unscoped));
return 0;
}
Which cast to use?
Ask yourself this... ... and do this
Can I use the correct type and not cast? Change types and do not cast
Does it compile with static_cast and you Use static_cast
know the original variable type?
Do I not know the original variable type and Use dynamic_cast and check for nullptr
want to check if a pointer or reference to base or handle std::bad_cast
class was originally some derived type?
Is my original variable modifiable or is it const Use const_cast or as_const
and won’t be modified?
Do I want to examine the bits of a type using a Use bit_cast (C++20 only)
different type of the same size?
Do I know exactly what is in memory and need Use reinterpret_cast
to treat it differently?
Which cast to use?
Ask yourself this... ... and do this
Can I use the correct type and not cast? Change types and do not cast
Does it compile with static_cast and you Use static_cast
know the original variable type?
Do I not know the original variable type and Use dynamic_cast and check for nullptr
want to check if a pointer or reference to base or handle std::bad_cast
class was originally some derived type?
Is my original variable modifiable or is it const Use const_cast or as_const
and won’t be modified?
Do I want to examine the bits of a type using a Use bit_cast (C++20 only)
different type of the same size?
Do I know exactly what is in memory and need Use reinterpret_cast
to treat it differently?
Which cast to use?
Ask yourself this... ... and do this
Can I use the correct type and not cast? Change types and do not cast
Does it compile with static_cast and you Use static_cast
know the original variable type?
Do I not know the original variable type and Use dynamic_cast and check for nullptr
want to check if a pointer or reference to base or handle std::bad_cast
class was originally some derived type?
Is my original variable modifiable or is it const Use const_cast or as_const
and won’t be modified?
Do I want to examine the bits of a type using a Use bit_cast (C++20 only)
different type of the same size?
Do I know exactly what is in memory and need Use reinterpret_cast
to treat it differently?
Which cast to use?
Ask yourself this... ... and do this
Can I use the correct type and not cast? Change types and do not cast
Does it compile with static_cast and you Use static_cast
know the original variable type?
Do I not know the original variable type and Use dynamic_cast and check for nullptr
want to check if a pointer or reference to base or handle std::bad_cast
class was originally some derived type?
Is my original variable modifiable or is it const Use const_cast or as_const
and won’t be modified?
Do I want to examine the bits of a type using a Use bit_cast (C++20 only)
different type of the same size?
Do I know exactly what is in memory and need Use reinterpret_cast
to treat it differently?
Which cast to use?
Ask yourself this... ... and do this
Can I use the correct type and not cast? Change types and do not cast
Does it compile with static_cast and you Use static_cast
know the original variable type?
Do I not know the original variable type and Use dynamic_cast and check for nullptr
want to check if a pointer or reference to base or handle std::bad_cast
class was originally some derived type?
Is my original variable modifiable or is it const Use const_cast or as_const
and won’t be modified?
Do I want to examine the bits of a type using a Use bit_cast (C++20 only)
different type of the same size?
Do I know exactly what is in memory and need Use reinterpret_cast
to treat it differently?
Which cast to use?
Ask yourself this... ... and do this
Can I use the correct type and not cast? Change types and do not cast
Does it compile with static_cast and you Use static_cast
know the original variable type?
Do I not know the original variable type and Use dynamic_cast and check for nullptr
want to check if a pointer or reference to base or handle std::bad_cast
class was originally some derived type?
Is my original variable modifiable or is it const Use const_cast or as_const
and won’t be modified?
Do I want to examine the bits of a type using a Use bit_cast (C++20 only)
different type of the same size?
Do I know exactly what is in memory and need Use reinterpret_cast
to treat it differently?
Which cast to use?
Ask yourself this... ... and do this
Can I use the correct type and not cast? Change types and do not cast
Does it compile with static_cast and you Use static_cast
know the original variable type?
Do I not know the original variable type and Use dynamic_cast and check for nullptr
want to check if a pointer or reference to base or handle std::bad_cast
class was originally some derived type?
Is my original variable modifiable or is it const Use const_cast or as_const
and won’t be modified?
Do I want to examine the bits of a type using a Use bit_cast (C++20 only)
different type of the same size?
Do I know exactly what is in memory and need Use reinterpret_cast
to treat it differently?
A possible new casting syntax

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2392r0.pdf
Resources:
Bjarne Stroustrup - The Design and Evolution of C++

Scott Meyers – Effective C++ (3 rd Edition)


Back To Basics THANKS
Casting

You might also like