Casting
Introduction
Casting involves converting one type to another. Some casts are valid and others may not be valid. We have the old style "C" style cast and the new casts: static_cast, dynamic_cast, const_cast, reinterpret_cast.File: cast1.cpp
#include <iostream> using namespace std ; int main() { int x1 = 10; float f1 = 10.5 ; x1 = (int)(f1); cout << "x1:" << x1 << endl ; //Undefined behavior //Not recommended const int x2 = 11 ; int* ptr1 = (int*) &x2 ; *ptr1 = 12 ; cout << "x2:" << x2 << endl ; } Output: $ rm a.exe ; g++ cast1.cpp ; ./a.exe 10 This is the old style "C" way of casting. It is better to use the new casting style as there is more compiler checking and it's easier to make out what the cast is doing. The "C" style could be a "static_cast" or a "reinterpret_cast" but it's hard to tell just from looking at the code.
File: cast2.cpp
#include <iostream> using namespace std ; class A { public: int x1 ; }; class B : public A { public: int x1B ; }; class C : public A { public: int x1C ; }; int main() { B bObject ; bObject.x1B = 22 ; A* aObject = &bObject ; //Not safe. as aObject contains bObject as the //underlying object C* cObject = (C*)aObject ; cout << cObject->x1C << endl ; } $ rm a.exe ; g++ cast2.cpp ; ./a.exe x1:10 22 The above shows another danger with the old style cast. B bObject ; bObject.x1B = 22 ; A* aObject = &bObject ; //Not safe. as aObject contains bObject as the //underlying object C* cObject = (C*)aObject ; cout << cObject->x1C << endl ; The compiler allows the above code and it also runs fine. However "aObject" is a "B" type object and not a "C" object. The better way to handle such casts is using "dynamic_cast" .
Static Cast
A static cast allows casts whose types are compatible. It will catch const type casting errors at compile time.File: static1.cpp
#include <iostream> using namespace std ; int main() { int x1 = 10; float f1 = 10.5 ; x1 = static_cast<int>(f1); cout << "x1:" << x1 << endl ; //Undefined behavior //Not recommended const int x2 = 11 ; int* ptr1 = static_cast<int*>(&x2) ; *ptr1 = 12 ; cout << "x2:" << x2 << endl ; } $ rm a.exe ; g++ static1.cpp ; ./a.exe static1.cpp: In function ‘int main()’: static1.cpp:15:18: error: invalid ‘static_cast’ from type ‘const int*’ to type ‘int*’ 15 | int* ptr1 = static_cast(&x2) ; | ^~~~~~~~~~~~~~~~~~~~~~ bash: ./a.exe: No such file or directory The "static_cast" catches the const cast issue. However it allows wrong inheritance casting.
File: static2.cpp
#include <iostream> using namespace std ; class A { public: int x1 ; }; class B : public A { public: int x1B ; }; class C : public A { public: int x1C ; }; int main() { int x1 = 10; float f1 = 10.5 ; x1 = static_cast<int>(f1); cout << "x1:" << x1 << endl ; B bObject ; bObject.x1B = 22 ; A* aObject = &bObject ; //Not valid as aObject contains bObject as the //underlying object C* cObject = static_cast<C*> (aObject) ; cout << cObject->x1C << endl ; } $ rm a.exe ; g++ static2.cpp ; ./a.exe x1:10 22 We notice that static_cast did not give us any compile time or run time errors. For this type of inheritance based casting we should use dynamic casting.
Dynamic Cast
File: dynamic1.cpp
#include <iostream> using namespace std ; class A { public: int x1 ; virtual void function1() { } }; class B : public A { public: int x1B ; }; class C : public A { public: int x1C ; }; int main() { B bObject ; bObject.x1B = 22 ; A* aObject = &bObject ; //Not valid as aObject contains bObject as the //underlying object C* cObject = dynamic_cast<C*> (aObject) ; printf( "Pointer value: %p\n" , cObject ) ; if ( cObject != NULL ) cout << cObject->x1C << endl ; A& refAObject = bObject ; try { C& refCObject = dynamic_cast<C&> ( refAObject ) ; } catch (const bad_cast& e) { cerr << "Exception: " << e.what() << endl; } } $ rm a.exe ; g++ dynamic1.cpp ; ./a.exe rm: cannot remove 'a.exe': No such file or directory Pointer value: 0x0 Exception: std::bad_cast The base class contains a virtual function: virtual void function1() { } This is necessary in order to use dynamic casting. Dynamic casting needs RTTI( Run-Time Type Information) which is implemented if there is a virtual function somewhere in the base class. Otherwise we will get a compiler error of the form: dynamic1.cpp: In function ‘int main()’: dynamic1.cpp:36:19: error: cannot ‘dynamic_cast’ ‘aObject’ (of type ‘class A*’) to type ‘class C*’ (source type is not polymorphic) 36 | C* cObject = dynamic_cast(aObject) ; C* cObject = dynamic_cast (aObject) ; printf( "Pointer value: %p\n" , cObject ) ; if ( cObject != NULL ) cout << cObject->x1C << endl ; In the above snippet; we know that the base class pointer "aObject" contains the B class pointer. A* aObject = &bObject ; So the type casting to "C^" should fail. With pointer casting the value of "cObject" is null as shown in the output. With references the behavior is slightly different. We have an exception that is thrown. A& refAObject = bObject ; try { C& refCObject = dynamic_cast ( refAObject ) ; } catch (const bad_cast& e) { cerr << "Exception: " << e.what() << endl; }
Const Cast
We can use the const cast to remove the constness of something. However we should not use it to remove the constness of the original variable if it was declared as a constant.File: const1.cpp
#include <iostream> using namespace std ; int main() { int x1 = 10; const int* ptr1 = &x1 ; //Can't do because ptr points to a constant //*ptr1 = 20 ; int* ptr2 = const_cast<int*>( ptr1 ); // Casting away constness *ptr2 = 20 ; cout << "x1:" << x1 << endl ; const int x3 = 10; int* ptr3 = const_cast<int*>( &x3 ) ; *ptr3 = 22 ; //Doesn't change the original x3 . Undefined //behavior. Not recommended cout << "x3:" << x3 << endl ; int x4 = 10; const int& constRef = x4 ; // Attempting to modify value through constRef directly will result in a compiler error // constRef = 20; // Error: assignment of read-only reference // Use const_cast to remove constness from the reference int& nonConstRef = const_cast<int&>(constRef); nonConstRef = 20; cout << "x4: " << x4 << endl; // Output: Value: 20 return 0; } Output: $ rm a.exe ; g++ const1.cpp ; ./a.exe x1:20 x3:10 x4: 20 We have: int x1 = 10; const int* ptr1 = &x1 ; We have a pointer to a constant "ptr1" that has been assigned the address of "x1" . So we cannot change it's value. //Can't do because ptr points to a constant //*ptr1 = 20 ; However we can remove the constness of the pointer by using the const cast. We can do it because the original variable is not a constant. int* ptr2 = const_cast( ptr1 ); // Casting away constness *ptr2 = 20 ; cout << "x1:" << x1 << endl ; Now in the next section in the code we have: const int x3 = 10; int* ptr3 = const_cast ( &x3 ) ; *ptr3 = 22 ; //Doesn't change the original x3 . Undefined //behavior. Not recommended cout << "x3:" << x3 << endl ; This leads to undefined behavior and doesn't actually change the value of the x3. We can also remove the constness of a constant reference. int x4 = 10; const int& constRef = x4 ; // Attempting to modify value through constRef directly will result in a compiler error // constRef = 20; // Error: assignment of read-only reference // Use const_cast to remove constness from the reference int& nonConstRef = const_cast (constRef); nonConstRef = 20; cout << "x4: " << x4 << endl; // Output: Value: 20
File: const2.cpp
#include <iostream> using namespace std ; int main() { int x1 = 10; float f1 = 10.5 ; //compiler error int x2 = const_cast<int>(x1); char x3 = const_cast<char>(x1); } $ rm a.exe ; g++ const2.cpp ; ./a.exe const2.cpp: In function ‘int main()’: const2.cpp:12:15: error: invalid use of ‘const_cast’ with type ‘int’, which is not a pointer, reference, nor a pointer-to-data-member type 12 | int x2 = const_cast(x1); | ^~~~~~~~~~~~~~~~~~~ const2.cpp:14:16: error: invalid use of ‘const_cast’ with type ‘char’, which is not a pointer, reference, nor a pointer-to-data-member type 14 | char x3 = const_cast (x1); | ^~~~~~~~~~~~~~~~~~~~ bash: ./a.exe: No such file or directory We cannot use const casting with normal types. The system expects the type to be a pointer or a reference. That makes sense as we do not want to remove the constness of an existing variable.
File: const3.cpp
#include <iostream> using namespace std ; #include <iostream> using namespace std; class student { private: int id ; public: // constructor student(int idP) : id(idP) {} // A const function. Cannot change the data // members of the class. //changes roll with the help of const_cast void function1() const { ( const_cast <student*> (this) )->id = 5; } int getId() { return id; } }; int main(void) { student s1(3); cout << "Old id number: " << s1.getId() << endl; s1.function1(); cout << "New id number: " << s1.getId() << endl; return 0; } $ rm a.exe ; g++ const3.cpp ; ./a.exe Old id number: 3 New id number: 5 The above program shows another use case of the const cast. We have a method in a class that is defined as const. That means the method cannot modify the class data variables unless that variable is defined as mutable. However we can remove that restriction by using const cast as shown in the function: void function1() const { ( const_castConst cast should be used in very limited circumstances. One such scenario may be old "C" code that has a function taking a non const parameter but that does not change the object.(this) )->id = 5; }
Reinterpret Cast
This cast is a binary cast. That means that the conversion is at the bit level. Suppose we have a float type number. That number is represented in a certain format. If we try to "reinterpret_cast" on it then it will be a problem because the "int" format is different. So the cmpiler does not allow it.The reinterpret_cast is a dangerous cast as it can convert between unrelated types due to casting at the bit level. In many cases the compiler will not allow the cast. There can be some scenarios where it might be useful such as: "accessing raw memory", "converting a type data to an array of bytes for serialization",
File: reinterpret1.cpp
#include <iostream> using namespace std ; int main() { int x1 = 10; float f1 = 10.5 ; x1 = reinterpret_cast<int>(f1); cout << x1 << endl ; } $ rm a.exe ; g++ reinterpret1.cpp ; ./a.exe rm: cannot remove 'a.exe': No such file or directory reinterpret1.cpp: In function ‘int main()’: reinterpret1.cpp:11:11: error: invalid cast from type ‘float’ to type ‘int’ 11 | x1 = reinterpret_castThe "reinterpret_cast" can only perform pointer-to-pointer conversions and reference-to-reference conversions (plus pointer-to-integer and integer-to-pointer conversions). If the above cast from float to int was allowed then the reinterpret cast will take the binary representation of the floating number and try to create an integer out of it; resulting in a gibberish value. It will not take the actual floating point value and discard the fraction part because it is jsut working at a binary level.(f1); | ^~~~~~~~~~~~~~~~~~~~~~~~~ bash: ./a.exe: No such file or directory Output: $ rm a.exe ; g++ reinterpret1.cpp ; ./a.exe reinterpret1.cpp: In function ‘int main()’: reinterpret1.cpp:11:11: error: invalid cast from type ‘float’ to type ‘int’ 11 | x1 = reinterpret_cast (f1);
File: reinterpret2.cpp
#include <iostream> using namespace std ; int main() { int x1 = 10; float f1 = 10.5 ; //x1 = reinterpret_cast<int>(f1); x1 = *reinterpret_cast<double*>( &f1 ); cout << x1 << endl ; x1 = (int)f1 ; cout << x1 << endl ; x1 = static_cast<int>( f1 ); cout << x1 << endl ; //pointer to pointer conversions int x2 = 15 ; int* p1 = &x2 ; char* c1 = reinterpret_cast<char*>(p1) ; cout << (int)(*c1) << endl ; //pointer to integer conversion long x3 = reinterpret_cast<long>(p1) ; cout << x3 << endl ; } Output: $ rm a.exe ; g++ reinterpret2.cpp ; ./a.exe -2147483648 10 10 15 34359725064 x1 = *reinterpret_cast( &f1 ); cout << x1 << endl ; Here we are trying to trick the compiler by doing a cast on the pointers and getting a value out for "x1" but we see that the value is not correct. The right way to do it is using the "C" style cast or the "C++" style cast. x1 = (int)f1 ; cout << x1 << endl ; x1 = static_cast ( f1 ); cout << x1 << endl ; The compiler will obtain the floating point value and then discard the fraction part and store the integral value in x1. //pointer to pointer conversions int x2 = 15 ; int* p1 = &x2 ; char* c1 = reinterpret_cast (p1) ; cout << (int)(*c1) << endl ; //pointer to integer conversion long x3 = reinterpret_cast (p1) ; cout << x3 << endl ; The above snippet shows how we can cast between an integer pointer and a character pointer and also casting a pointer to an integral value.
File: reinterpret3.cpp
#include <iostream> #include <cstdint> using namespace std; // Hash function for pointers using reinterpret_cast template <typename T> struct PointerHash { size_t operator()(const T* pointer) const { uintptr_t addr = reinterpret_cast<uintptr_t>(pointer); // Ensure the address fits within size_t #if SIZE_MAX < UINTPTR_MAX addr %= SIZE_MAX; #endif return addr; } }; int main() { int x = 10; int* ptr = &x; PointerHash<int> hash_func; size_t hash_value = hash_func(ptr); cout << "Hash value: " << hash_value << endl; return 0; } Output: $ rm a.exe ; g++ reinterpret3.cpp ; ./a.exe Hash value: 34359725084 The above shows how we can use "reinterpret_cast" in a hashing function. The "uintptr_t" type is a unsigned integer type. that can store a data pointer.