The typecasting is the feature which makes C++ more type-safe, robust & may convince you to use it over C. But this is also a more underrated topic when you are a newbie or moving from C background. Hence, I come up with an article on it . Here, we will not only see the C++ typecasting but we will also cover why we need it & cheat codes for C developers to remember & employ it easily. Although I am not an expert but this is what I have learned so far from various sources & 5+ yrs of industry experience.
/!\: This article has been originally published on my blog. If you are interested in receiving my latest articles, please sign up to my newsletter.
In C++, there are 5 different types of casts: C-style casts, static_cast, const_cast, dynamic_cast, and reinterpret_cast.
I usually start with "Why we need it?", but this time first we quickly go through some jargons & I will end this article with some of CPP core guidelines.
Jargons you need to face
- 
Implicit conversion: where the compiler automatically typecast. Like float f = 3;, here compiler will not complain but directly transform3which is of type integer intofloat& assign tof.
- 
Explicit conversions: where the developer uses a casting operator to direct the conversion. All types of manual casting fall under the explicit type conversions category. Like int * p = (int*)std::malloc(10);, here we explicitly castingvoid*toint*.
- 
l-value: an identifier which represents memory location. For example, variable name,*ptrwhereptrpoints to a memory location, etc.
- 
r-value: a value which is notl-value,r-valueappear on the right-hand side of assignment(=) operator. Like
int a = 5; // 5 = r-value, q = p + 5; // p + 5 is r-value
Note: Although there are some exceptions & more to learn on l-value & r-value which I have discussed here. 
Why we need typecasting?
- Data is a representation of the bits(0s &1s) in memory.
- Data-type is compiler directive which tells the compiler how to store & process particular data.
- 
uint32_t a = 5;by this statement you can presume that 4 bytes will be reserved in your memory & upon execution, it will store0000 0000 0000 0000 0000 0000 0000 0101data bits in that memory location. This was plain & simple.
- Let's go a bit further, float f = 3.0;this statement will also reserve 4 bytes in memory & store data bits in form of 1). the sign bit, 2). exponent & 3). mantissa. Recall how floating-point numbers are stored in memory.
- But when you write like float f = 3;, the compiler will be confused that how to store an integer value in float type of memory.
- So it will automatically presume(Implicit conversion here) that you want to store 3.0rather than3which is technically same from the human point of view but it's different when you think from computer memory perspective cause they stored differently.
- There are many such scenarios where you provide data to store in memory which used to represent different data type.
- For example, in the following example, you are trying to assign an object of type Binto an object of typeA
class A{};
class B{};
int main ()
{
  B b;
  A a = b; 
  return 0;
}
- In such scenario compiler can not presume anything & simply throws a compilation error:
exit status 1
error: no viable conversion from 'B' to 'A'
  A a = b;
    ^   ~
note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'B' to 'const A &' for 1st argument
class A{};
      ^
note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'B' to 'A &&' for 1st argument
class A{};
      ^
1 error generated.
- But when you define a conversion operator as follows:
class B {
public:
  operator A(){
    cout<<"CONVERSION OPERATOR\n";
    return A();
  } 
};
- The compiler will simply call this member function & won't throw any error because programmer explicitly mentioning that this is how he/she wants to convert.
5️⃣ C++ type casting with example for C developers
C-style casts
int main() { 
    float res = 10 / 4;
    cout<<res<<endl;
    return 0; 
}
- When you will try to run the above code, you will get 2as output which we didn't expect. To initializeresvariable correctly we need to typecast using float as follows:
float res = (float)10 / 4;
- Now your answer will be 2.5. This type of casting is very simple & straight forward as it appears.
- You can also write above casting in C++ as:
float res = float(10) / 4;
- C-style casts can change a data type without changing the underlying memory representation which may lead to garbage results.
static_cast 
- If you are C developer like me, then this will be your best goto C++ cast which fits in most of the example like:
int * p = std::malloc(10);
- When you try to compile above code using C compiler it works fine. But C++ compiler is not kind enough. It will throw an error as follows :
exit status 1
error: cannot initialize a variable of type 'int *' with an rvalue of type 'void *'
  int * p = std::malloc(10);
        ^   ~~~~~~~~~~
1 error generated.
- The first thing that comes to your mind is the C-style cast:
int * p = (int*)std::malloc(10);
- This will work, but this style of the cast is not recommended in C++. static_casthandles implicit conversions like this. We will primarily use it for converting in places where implicit conversions fail, such as std::malloc.
int * p = static_cast<int*>(std::malloc(10));
- The main advantage of static_castis that it provides compile-time type checking, making it harder to make an inadvertent error. Let's understand this with C++ example:
class B {};
class D : public B {};
class X {};
int main()
{
  D* d = new D;
  B* b = static_cast<B*>(d); // this works
  X* x = static_cast<X*>(d); // ERROR - Won't compile
  return 0;
}
- As you can see, there is no easy way to distinguish between the two situations without knowing a lot about all the classes involved.
- Another problem with the C-style casts is that it is too hard to locate. In complex expressions, it can be very hard to see C-style casts e.g. T(something)syntax is equivalent to(T)something.
const_cast 
- Now we will directly jump to example. No theory> can explain this better than example.
1. Ignore constness
int i = 0; const int& ref = i; const int* ptr = &i; *ptr = 3; // Not OK const_cast<int&>(ref) = 3; //OK *const_cast<int*>(ptr) = 3; //OK
- You are allowed to modify i, because of the object(ihere) being assigned to, is notconst. If you add const qualifier toi, code will compile, but its behaviour will be undefined (which can mean anything from "it works just fine" to "the program will crash>".)
2. Modifying data member using const this pointer
- 
const_castcan be used to change non-const class members by a method in which this pointer declared as const. - This can also be useful when overloading member functions based onconst, for instance:
class X
{
public:
    int var;
    void changeAndPrint(int temp) const
    {
        this->var = temp;                    // Throw compilation error
        (const_cast<X *>(this))->var = temp; // Works fine
    }
    void changeAndPrint(int *temp)
    {
        // Do some stuff
    }
};
int main()
{
    int a = 4;
    X x;
    x.changeAndPrint(&a);
    x.changeAndPrint(5);
    cout << x.var << endl;
    return 0;
}
3. Pass const argument to a function which accepts only non-const argument
- 
const_castcan also be used to pass const data to a function that doesn’t receive const argument. See the following code:
int fun(int* ptr) 
{ 
    return (*ptr + 10); 
} 
int main(void) 
{ 
    const int val = 10; 
    cout << fun(const_cast <int *>(&val)); 
    return 0; 
} 
4. Castaway volatile attribute
- 
const_castcan also be used to cast awayvolatileattribute. Whatever we discussed above inconst_castis also valid forvolatilekeyword.
dynamic_cast 
- 
dynamic_castuses the type checking at runtime in contrary tostatic_castwhich does it at compile time.dynamic_castis more useful when you don't know the type of input which it represents. Let assume:
Base* CreateRandom()
{
    if( (rand()%2) == 0 )
        return new Derived1;
    else
        return new Derived2;
}
Base* base = CreateRandom();
- As you can see, we don't know which object will be returned by CreateRandom()at run time but you want to executeMethod1()ofDerived1if it returnsDerived1. So in this scenario, you can usedynamic_castas follows
Derived1 *pD1 = dynamic_cast<Derived1 *>(base);
if (pD1){
    pD1->Method1();
}
- In case, if input of dynamic_castdoes not point to valid data, it will returnnullptrfor pointers or throw astd::bad_castexception for references. In order to work withdynamic_cast, your classes must be polymorphic type i.e. must include at least one virtual methods.
- 
dynamic_casttake advantage ofRTTI(Run Time Type Identification) mechanism.
reinterpret_cast 
- 
reinterpret_castis a compiler directive which tells the compiler to treat the current type as a new type.
- You can use reinterpret_castto cast any pointer or integral type to any other pointer or integral type.
- This can lead to dangerous situations: nothing will stop you from converting an intto anstd::string*.
- You will use reinterpret_castin your embedded systems. A common scenario wherereinterpret_castapplies is converting betweenuintptr_tand an actual pointer or between:
error: static_cast from 'int *' to 'uintptr_t'
      (aka 'unsigned long') is not allowed
        uintptr_t ptr = static_cast<uintptr_t>(p);
                        ^~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
- Instead, use this:
uintptr_t ptr = reinterpret_cast<uintptr_t>(p);
I have tried to cover most of the intricacies to clear the main concept behind different typecasting, but still, there might be a chance that I may miss some. So, this is it for C++ type casting with an example for C developers. Let's quickly recap:
Cheat code for C developers moving to C++ on type casting
After reading all this you may confuse on what to use & when! That's why I have created this cheat code
- Avoid C-style casts. Be sure about what you want while casting.
- Use static_castwherever you were using C-style cast.
- Use dynamic_castwith polymorphic classes. Keep in mind that only usedynamic_caston classes with at least one virtual member in the inheritance hierarchy.
- Use const_castwhen you need to removeconstorvolatilequalifiers.
-  Use reinterpret_castwhen you have no options.
Note: const_cast and reinterpret_cast should generally be avoided because they can be harmful if used incorrectly. Don't use it unless you have a very good reason to use them.
Some of C++ core guidelines
- P.4: Ideally, a program should be statically (compile-time) type safe
- ES.48: Avoid casts
- ES.49: If you must use a cast, use a named cast
- 
ES.50: Don’t cast away const
- 
C.146: Use dynamic_castwhere class hierarchy navigation is unavoidable
- 
C.147: Use dynamic_castto a reference type when failure to find the required class is considered an error
- 
C.148: Use dynamic_castto a pointer type when failure to find the required class is considered a valid alternative
Other related articles
- 21 new features of Modern C++ to use in your project
- All about lambda functions in C++(from C++11 to C++17)
- How floating-point no is stored in memory?
Have any suggestions, query or wants to say Hi? Take the Pressure Off, you are just a click away.🖱️
 
 
              
 
    
Top comments (11)
Most of explanations sound not very clear to me. For instance, regarding constness. It would be great to see more comments about given examples touching some basics for the people who, for instance, are trying to master C++ after C#, etc. I think it would be more efficient than addressing such people to other references, books...
isn't it subjective matter?
Every matter is subjective ;) We all express our thoughts and wishes.
Erm,
(float)10/2is not 2.5 coliru.stacked-crooked.com/a/77b57...Also, "you can also write
(float)10likefloat(10)isn't correct and makes it seem as if they're interchangable. I know some later text mentions the danger (eg(float) "danger"s) but by then the damage might be doneYes...! You are correct. But that is what my point is.
You shouldn't use C-style cast.
Use
newinstead ofmallocanddeleteinstead offree.There is malloc and free in C++ i.e.
std::mallocandstd::freerespectively.Hey man!
Thanks for pointing it out.
You should update the following as well.
done
Finally! This is exactly the article I have been looking for in the past year. Thank you for this!
Thanks, Dood...