Type Casting and Type Conversion In C++: Part 2

Type Casting and Type Conversion In C++: Part 2
Type Casting and Type Conversion In C++: Part 2

Introduction

In this article, we’ll be continuing the discussion on Type Casting and Type Conversion in C++. So far, we’ve discussed Implicit Conversion and C-style Type Casting. If you have prior knowledge of these concepts, then go ahead. Otherwise, we recommend you to check our article on Type Casting and Type Conversion in C++. 

Before diving into Typecasting, let us understand, What is the Cast operator?

A cast is a special operator that coerces one data type to be converted into another. As an operator, a cast is unary and has the same precedence as any other unary operator.

Static_Cast in C++ 

In static_cast typecasting, the static_cast() is used to cast the primitive data types and cast the pointers and references. As the name suggests, the casting is performed at the compilation time.

Syntax:

new_type = static_cast< new_type > (expression);

It performs implicit conversions between types.

#include <iostream>
using namespace std;
int main()
{
    float f = 6.5;
    int i ;
    i = f;                                   //Implicit Type Conversion
    cout<<"i = "<<i<<endl;
    i = static_cast<int> (f);      //Static_cast Conversion
    cout<<"i = "<<i<<endl;
    return 0;
}

OUTPUT

i = 6
i = 6

Why use static_cast when Implicit Conversion is involved?

The only reason is to improve user readability. When you have a giant code, then it’s easy to search the static_cast keyword rather than searching for C-style Casting.

Now, let’s discuss the use cases of static_cast in Type Casting and Type Conversion in C++:

1, To prevent Dangerous casts, static_cast is best to use. Static_cast is more restrictive than C-style casting. Let’s take a look at an example:-

#include <iostream>
using namespace std;
int main()
{
    char c;                        // 1-byte data
    int *p1 = (int*)&c;        // C-style casting
    *p1=5;                      //Invalid Conversion 
                                   // Passes at compile time, fail at run time

    int *p2 = static_cast<int*>(&c); //static_cast will throw the Compile time error
    
    return 0;
}

OUTPUT

main.cpp:9:35: error: invalid static_cast from type ‘char*’ to type ‘int*’

Explanation: In the above example, we try to cast the char* to type int*, an invalid conversion. The data type to which it is pointing does not take the same amount of memory. For example, char takes 1 byte, int takes 4 bytes (in modern compilers), and pointer variable stores the address of the first byte of that memory location, so by specifying the data type of pointer variable, we tell him to go up till that number of bytes which that data type variable takes. If we let the c-style cast handle the casting, the code will pass through the compilation step, risky.

2. Static_cast prevents cast from being derived from a private base pointer.

#include <iostream>
using namespace std;
class Parent{};
class Child: private Parent{};  // privately inherited, must be inaccessible
int main()
{
    Child c;
    Parent *pp = (Parent*)&c; //Runs at Compile time 
    
    Parent *pp2 = static_cast<Parent*> (&c); //FAIL at compile time
    return 0;
}

OUTPUT

main.cpp:10:43: error: ‘Parent’ is an inaccessible base of ‘Child’

Explanation: In the above example, we have privately inherited the Parent class into the Child class. As we all know, the Child (or derived class) cannot access the methods or features of its base class since they are inherited privately. However, in this case, if we use C-style casting, it will pass the compilation process, which is incorrect. Using static_cast prevents the code from passing the compilation process.

3. Another use case- static_cast should be preferred when converting something to (void*) or from (void*).

    int i=10;
    void* v = static_cast<void*> (&i);  // to (void*)
    int *p = static_cast<int*>(v);         // from (void*)

A void pointer is a pointer with no associated data type. A void pointer can carry any type of address and can be typecast to any type.

Dynamic_cast in C++

To understand Dynamic_cast, we need to understand RTTI.

RTTI( Run Time Type Identification)-

  • It provides a standard way for a program to determine the type of object during runtime.
  • RTTI is provided through two operators:-
    • The typeid operator returns the actual type of object referred to by a pointer( or reference).
    • The dynamic_cast operator safely converts from the pointer ( or reference) to a base type or a derived type.

          Let’s take an example:-

We have three classes where Animal is a base class, Cat and Dog are the derived ones.

Animal *bp1 = new Cat; // Allowed

Animal *bp2 = new Dog; // Correct

Dog *dp = dyanmic-cast<Dog*>(bp1); // Returns the null value

The bp1 pointer is pointing (or referencing) to the Cat Class, as we can see. We’re also casting it to the Dog class, which isn’t permitted in Inheritance. A Child( or derived class) can only have attributes from its Parent class or its own class. As a result, the null value indicates an invalid conversion. Static_cast, on the other hand, is unable to discern this type of conversion. Nevertheless, dynamic_cast under the Type Casting and Type Conversion in C++ will let us know about the incompatible conversions.

Syntax:

new_type = dynamic_cast<new_type> (Expression)

Properties of dynamic_cast:

  • It involves a run-time type check.
  • The base class has to be polymorphic, which means it must have a virtual function.
  • On successful conversion, it returns a value of new_type.
  • On failure, if new_type is a pointer – returns a null pointer or if new_type is a reference- throws the bad_cast exception.
  • It only allows valid conversions.

Let’s see the Implementation now:

#include<bits/stdc++.h>
using namespace std;
class Base{
    virtual void f(){
        
    }
};
class Derived : public Base{};
int main(){
    Derived dobj;
    Base bobj;
    Base & rb = dynamic_cast<Base&>(dobj);
    Derived & rd = dynamic_cast<Derived*>(bobj); // exception bad_cast
        
    return 0;
}

OUTPUT

main.cpp:13:51: error: cannot dynamic_cast ‘bobj’ (of type ‘class Base’) to type ‘class Derived*’ (source is not a pointer)

Explanation: In the above example, we attempt to cast the base object into the derived object, which is impossible. Because giving it to a derived class reference would be equivalent to saying, “Base class is a competent substitute for a derived class, it can do everything the derived class can do,” which is false. Let’s say we have three classes:

The person class is the base class, whereas the Student and the Faculty classes are the derived ones. The student and the Faculty classes are the persons. But, a person cannot always be the student, which we are attempting to accomplish in the preceding code. Hence, it is an invalid conversion. 

Const_cast in C++

It is used to cast away the constness of the variables. For example:- If the programmer wants to change the constant value of the variable at a particular point, then const_cast is best to use.

Syntax:

new_type = const_cast< new_type > (expression );

1. Passing const data to a function that doesn’t receive the const value.

#include<bits/stdc++.h>
using namespace std;
int fun( int* ptr){
    return (*ptr);
}
int main(){
    const int val = 5;
    const int *ptr = &val;
    int* ptr1 = const_cast<int*>(ptr);//conversion from const to non const
    cout<<fun(ptr1);
    
    return 0;
}

OUTPUT     

5

Explanation: With the aid of const cast, we are attempting to remove the constness of a const variable in the above code. This is useful when the programmer wishes to allocate the value of a const variable to a non-const variable. 

2. Undefined behaviour to modify a value initially declared as const.

#include<bits/stdc++.h>
using namespace std;

int main(){
    const int a = 10;
    const int*p1 = &a;
    int *p2 = const_cast<int*>(p1); 
    *p2 = 20;
    cout<<"Value at a =  "<<a<<endl; //Supposed to be same as p2
    cout<<"Value at p2  = "<<*p2<<endl; // supposed to be same as a
        
    return 0;
}

OUTPUT

Value at a = 10
Value at p2  = 20

Oops, the output is unexpected. Let’s understand why? 

“Except that any class member declared mutable can be modified, any attempt to modify a const object during its lifetime results in undefined behaviour.” The compiler treats the const variable value as it was initialised all over the program. 

Reinterpret_cast in C++

Reinterpret_cast in c++ allows any pointer to be converted into any other pointer type. It also permits any integral type to be converted into any pointer type and vice versa.

Syntax:

new_type = reinterpret_cast< new_type > (expression);

1. Program to convert integer pointer into character pointer.

#include<bits/stdc++.h>
using namespace std;

int main(){
    int a = 70; // ASCII value of F = 70
    int *int_pointer = &a;
    char* char_pointer = reinterpret_cast<char *>(int_pointer);
    cout<<*char_pointer<<endl;
    return 0;
}

OUTPUT

F

Explanation: In the above example, the conversion of the integer pointer into the character pointer has taken place with the aid of reinterpret_cast.

2. Cannot cast away the const, volatile or unaligned attributes.

#include<bits/stdc++.h>
using namespace std;

int main(){
   int a = 70;
   const int *c = &a;
   char *pc = reinterpret_cast<char*> (c); // fails at compile time
       
   const char* pc2 = reinterpret_cast<const char*>(c); // pass
   return 0;
}

OUTPUT

main.cpp:7:45: error: reinterpret_cast from type ‘const int*’ to type ‘char*’ casts away qualifiers

Explanation: In the above example, we are attempting to convert a const int pointer variable into a non-const char pointer, which is not feasible; as a result, the conversion is incompatible, resulting in a compilation error.

3. Reinterpret_cast in C++ is widely used while working with bits.

struct S{
    int i1;          // 4 bytes
    int i2;          // 4 bytes
    char c;        // 1 byte
    bool b;       // 1 byte
}
S s;
s.i1 = 10;
s.i2 = 20;
s.c = 'A';
s.b = true;
   int*ps = reinterpret_cast<int*> (&s);
   cout<<*ps<<endl;
   ps++; // increment the pointer to the next byte
   cout<<*ps<<endl;

    OUTPUT

10
20

If we do ps++ again, it will jump to the next byte, but here the pointer is of integral type; so it will assume to modify the 4 bytes; hence, we need to cast it to the character type.

  ps++;
   char *pSc = reinterpret_cast<char *>(ps);  
   cout<<*pSc<<endl;  // prints A
   pSc++;
   bool *pSb = reinterpret_cast<bool *>(pSc);
   cout<<*pSb<<endl;  // prints 1

OUTPUT

A
1

Now, it is your turn to play with bits.

Frequently Asked Questions

Differentiate static_cast and dynamic_cast in C++.

static_cast
1. Compile Time casting
2. Base class doesn’t need to be polymorphic
3. Throws a Compile time error

dynamic_cast
1. Run-time casting
2. Base class must be polymorphic
3. On failure, returns the null pointer

Why is reinterpret_cast considered an Inherently unsafe conversion?

The reinterpret_cast operator can be used for conversions such as char* to int* or One_Class* to Unrelated_Class*, which are inherently unsafe.

How do you typecast in C++?

Casting is a conversion process wherein data is changed from one type to another. Type Casting and Type Conversion in C++ can be performed Implicitly and Explicitly. Implicit Conversion is an automatic process done by the compiler; on the other hand, Explicit Conversion needs the user-involvement.

Key Takeaways

To summarise the discussion, Type Casting and Type Conversion in C++ are necessary when the programmer wants to change the data from one type to another without changing the significance of the value stored inside the variable. 

Moreover, a cast is a way of explicitly letting the compiler know that you want to intend the Conversion, and it can also further cause data loss. Using Type Casting operators instead of C-style Conversion embraces the readability of the program. 

Don’t sit still, do practice these programs in a Codestudio for a better grasp. Practise Practise Practise!

By: Alisha Chhabra