Home C++ Introduction Decisions Loops Input/Output Functions Stack and Heap References Arrays Searching and Sorting Recursion Pointers Character and Strings Structures Classes Inheritance Exceptions Templatess Casting STL Modern C++ Misc Books ----

Casting


Contents

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_cast  (this) )->id = 5;
	}


Const 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.

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_cast(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);

The "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.
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.