Back To Basics Casting
Back To Basics Casting
Casting
O r: how to s ubv ert the ty pe s y s tem
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!”
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!”
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!”
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!”
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!”
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!”
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!”
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; };
SuperInt si;
int i = si;
Explicit Type Conversions
Explicit Type Conversions
a.k.a Casts
C-style cast
int main() {
const tree oak;
car mustang;
int main() {
const tree oak;
car mustang;
int main() {
const tree oak;
car mustang;
int main() {
const tree oak;
car mustang;
int main() {
const tree oak;
car mustang;
int main() {
const tree oak;
car mustang;
int main() {
const tree oak;
car mustang;
int main() {
const tree oak;
car mustang;
int main() {
const tree oak;
car mustang;
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. static_cast
2. const_cast
3. dynamic_cast
4. reinterpret_cast
*keywords
static_cast<T>
int main() {
E e;
A a = static_cast<A>(e);
return 0;
}
static_cast<T> multiple hops
int main() {
Derived d;
Derived* derivedptr = &d;
Base1* base1ptr = static_cast<Base1*>(&d);
CheckSame(derivedptr, base1ptr);
return 0;
}
static_cast<T> and inheritance
int main() {
Derived d;
Derived* derivedptr = &d;
Base1* base1ptr = static_cast<Base1*>(&d);
CheckSame(derivedptr, base1ptr);
return 0;
}
static_cast<T> and inheritance
int main() {
Derived d;
Derived* derivedptr = &d;
Base1* base1ptr = static_cast<Base1*>(&d);
CheckSame(derivedptr, base1ptr);
return 0;
}
static_cast<T> and inheritance
int main() {
Derived d;
Derived* derivedptr = &d;
Base1* base1ptr = static_cast<Base1*>(&d);
CheckSame(derivedptr, base1ptr);
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>
int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>
int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>
int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>
int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>
int j = 4;
const int* cj = &j;
modify_pointer(const_cast<int*>(cj));
printf("Modified %d\r\n", i);
return 0;
}
const_cast<T>
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)
struct Page {
std::vector<Widget> mWidgetList;
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;
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;
Code::Bytes
dynamic_cast can be expensive
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
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
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
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
1. bit_cast
2. move/move_if_noexcept
3. forward
4. as_const
5. to_underlying (C++23)
bit_cast<T>
*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>
*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
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
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++