DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • How Developers Can Create “Sticky” Products
  • The Cypress Edge: Next-Level Testing Strategies for React Developers
  • Data Governance Essentials: Glossaries, Catalogs, and Lineage (Part 5)
  • Tech Trends 2024: Highlights on the Current Tech Industry From a Developer

Trending

  • Strategies for Securing E-Commerce Applications
  • Designing AI Multi-Agent Systems in Java
  • Prioritizing Cloud Security Risks: A Developer's Guide to Tackling Security Debt
  • Developers Beware: Slopsquatting and Vibe Coding Can Increase Risk of AI-Powered Attacks
  1. DZone
  2. Data Engineering
  3. Data
  4. C++ Type Casting With Example for C Developers

C++ Type Casting With Example for C Developers

By 
Vishal Chovatiya user avatar
Vishal Chovatiya
·
Apr. 28, 20 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
8.5K Views

Join the DZone community and get the full member experience.

Join For Free

The typecasting is a feature that makes C++ more type-safe, robust, and may convince you to use it over C. But this is also a more underrated topic when you are a newbie or moving from a C background. Hence, I came up with an article on it. Here, we will not only see the  C++ type casting with examples, but we will also cover how to remember and employ it easily. Although I am not an expert, but this is what I have learned so far from various sources and 5+ yrs of industry experience. 

In C++, there are 5 different types of casts: C-style casts, static_cast, const_cast, dynamic_cast, and reinterpret_cast.

Jargon You Need to Know

  1. Implicit conversion: where the compiler automatically typecast. Like float f = 3;. Here, the compiler will not complain but directly transform 3, which is of type integer into float and assign to f.
  2. 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 casting void* to int*.
  3. l-value: an identifier which represents memory location. For example, variable name, *ptr where ptr points to a memory location, etc.
  4. r-value: a value which is not l-value,  r-value appears on the right-hand side of the assignment(=) operator. Like
C++
xxxxxxxxxx
1
 
1
int a = 5; // 5 = r-value, 
2
q = p + 5; // p + 5 is r-value


Note: There are some exceptions and more to learn on lvalue, rvalue and their references in C++. 

Why Do We Need Typecasting?

  • Data is a representation of the bits(0s and 1s) in memory.
  • Data-type is compiler directive that tells the compiler how to store and process particular data.
  • uint32_t a = 5; with this statement, you can presume that 4 bytes will be reserved in your memory, and upon execution, it will store 0000 0000 0000 0000 0000 0000 0000 0101 data bits in that memory location. This was plain and simple. 
  • Let's go a bit further. float f = 3.0; — this statement will also reserve 4 bytes in memory and store data bits in the form of 1). the sign bit, 2). exponent and 3). mantissa. Recall how floating-point numbers are stored in memory.
  • But when you write something 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.0 rather than 3, which is technically the same from the human point of view, but it's different when you think from computer memory perspective, as they are stored differently.
  • There are many such scenarios where you provide data to store in memory which is used to represent a different data type.
  • For example, in the following example, you are trying to assign an object of type B into an object of type A
C++
xxxxxxxxxx
1
 
1
class A{};
2
class B{};
3
4
int main ()
5
{
6
  B b;
7
  A a = b; 
8
  return 0;
9
}


In such a scenario, the compiler can not presume anything and simply throws a compilation error:

C++
xxxxxxxxxx
1
11
 
1
exit status 1
2
error: no viable conversion from 'B' to 'A'
3
  A a = b;
4
    ^   ~
5
note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'B' to 'const A &' for 1st argument
6
class A{};
7
      ^
8
note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'B' to 'A &&' for 1st argument
9
class A{};
10
      ^
11
1 error generated.


But, when you define a conversion operator as follows:

C++
xxxxxxxxxx
1
 
1
class B {
2
public:
3
  operator A(){
4
    cout<<"CONVERSION OPERATOR\n";
5
    return A();
6
  } 
7
};


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.

C++ Type Casting With Example for C Developers

[su_tabs][su_tab title="C-style casts" disabled="no" anchor="" url="" target="blank" class=""]

C-Style Casts

C++
xxxxxxxxxx
1
 
1
int main() { 
2
    float res = 10 / 4;
3
    cout<<res<<endl;
4
    return 0; 
5
}


When you try to run the above code, you will get 2 as output, which we didn't expect. To initialize the res variable correctly, we need to typecast using float as follows:

C++
xxxxxxxxxx
1
 
1
float res = (float)10 / 4;


Now, your answer will be 2.5. This type of casting is very simple and straight forward. You can also write the above casting in C++ as:

C++
xxxxxxxxxx
1
 
1
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.

[/su_tab] [su_tab title="static_cast" disabled="no" anchor="" url="" target="blank" class=""]

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:

C++
xxxxxxxxxx
1
 
1
int * p = std::malloc(10);


When you try to compile the above code using a C compiler, it works fine. But a C++ compiler, will not be so kind. It will throw an error as follows :

C++
xxxxxxxxxx
1
 
1
exit status 1
2
error: cannot initialize a variable of type 'int *' with an rvalue of type 'void *'
3
  int * p = std::malloc(10);
4
        ^   ~~~~~~~~~~
5
1 error generated.


The first thing that comes to your mind is the C-style cast:

C++
xxxxxxxxxx
1
 
1
int * p = (int*)std::malloc(10);


This will work, but C-style cast is not recommended in C++. static_cast handles implicit conversions like this. We will primarily use it for converting in places where implicit conversions fail, such as std::malloc.

C++
xxxxxxxxxx
1
 
1
int * p = static_cast<int*>(std::malloc(10));


The main advantage of static_cast is that it provides compile-time type checking, making it harder to make an inadvertent error. Let's understand this with C++ example:

C++
xxxxxxxxxx
1
11
 
1
class B {};
2
class D : public B {};
3
class X {};
4
5
int main()
6
{
7
  D* d = new D;
8
  B* b = static_cast<B*>(d); // this works
9
  X* x = static_cast<X*>(d); // ERROR - Won't compile
10
  return 0;
11
}


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. the T(something) syntax is equivalent to (T)something). 

[/su_tab] [su_tab title="const_cast" disabled="no" anchor="" url="" target="blank" class=""]

Const_cast 

Now, we will directly jump to example. No theory can explain this better than an example. 

1. Ignore constness 

C++
xxxxxxxxxx
1
 
1
int i = 0;
2
const int& ref = i;
3
const int* ptr = &i;
4
5
*ptr = 3; // Not OK
6
const_cast<int&>(ref) = 3;  //OK
7
*const_cast<int*>(ptr) = 3; //OK


You are allowed to modify i because of the object (i here) being assigned to, is not const. If you add a const qualifier to i the code will compile, but its behavior 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_cast can 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 on const, for instance:

C++
xxxxxxxxxx
1
23
 
1
class X
2
{
3
public:
4
    int var;
5
    void changeAndPrint(int temp) const
6
    {
7
        this->var = temp;                    // Throw compilation error
8
        (const_cast<X *>(this))->var = temp; // Works fine
9
    }
10
    void changeAndPrint(int *temp)
11
    {
12
        // Do some stuff
13
    }
14
};
15
int main()
16
{
17
    int a = 4;
18
    X x;
19
    x.changeAndPrint(&a);
20
    x.changeAndPrint(5);
21
    cout << x.var << endl;
22
    return 0;
23
}


3. Pass const argument to a function which accepts only non-const argument

const_cast can also be used to pass const data to a function that doesn’t receive const argument. See the following code:

C++
xxxxxxxxxx
1
11
 
1
int fun(int* ptr) 
2
{ 
3
    return (*ptr + 10); 
4
} 
5
6
int main(void) 
7
{ 
8
    const int val = 10; 
9
    cout << fun(const_cast <int *>(&val)); 
10
    return 0; 
11
} 


4. Castaway volatile attribute

const_cast can also be used to cast away volatile attribute. Whatever we discussed above in const_cast is also valid for the volatile keyword.

[/su_tab] [su_tab title="dynamic_cast" disabled="no" anchor="" url="" target="blank" class=""]

Dynamic_cast

dynamic_cast uses the type checking at runtime in contrary to static_cast which does it at compile time. dynamic_cast is more useful when you don't know the type of input that it represents. Let's assume:

C++
xxxxxxxxxx
1
 
1
Base* CreateRandom()
2
{
3
    if( (rand()%2) == 0 )
4
        return new Derived1;
5
    else
6
        return new Derived2;
7
}
8
9
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 execute Method1() of Derived1 if it returns Derived1. So in this scenario, you can use dynamic_cast as follows

C++
xxxxxxxxxx
1
 
1
Derived1 *pD1 = dynamic_cast<Derived1 *>(base);
2
if (pD1){
3
    pD1->Method1();
4
}


In case, if the input of dynamic_cast does not point to valid data, it will return nullptr for pointers or throw a std::bad_cast exception for references. In order to work with dynamic_cast, your classes must be polymorphic type i.e. must include at least one virtual method.

dynamic_cast take advantage of RTTI(Run Time Type Identification) mechanism.

[/su_tab] [su_tab title="reinterpret_cast" disabled="no" anchor="" url="" target="blank" class=""]

Reinterpret_cast

reinterpret_cast converts between types by reinterpreting the underlying bit pattern.  You can use reinterpret_cast to 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 int to an std::string*.

You can use reinterpret_cast in your embedded systems. A common scenario where reinterpret_cast applies is converting between uintptr_t and an actual pointer or between:

C++
xxxxxxxxxx
1
 
1
error: static_cast from 'int *' to 'uintptr_t'
2
      (aka 'unsigned long') is not allowed
3
        uintptr_t ptr = static_cast<uintptr_t>(p);
4
                        ^~~~~~~~~~~~~~~~~~~~~~~~~
5
1 error generated.


Instead, use this:

C++
xxxxxxxxxx
1
 
1
uintptr_t ptr = reinterpret_cast<uintptr_t>(p);


[/su_tab][/su_tabs]

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 examples for C developers. Let's quickly recap:

Cheat Sheet for C Developers Moving to C++ on Type Casting

After reading all this, you may confused on what to use & when! That's why I have created this cheat sheet:

  • Avoid C-style casts. Be sure about what you want while casting.
  • Use static_cast wherever you were using C-style cast.
  • Use dynamic_cast with polymorphic classes. Keep in mind that you should only use dynamic_cast in classes with at least one virtual member in the inheritance hierarchy.
  • Use const_cast when you need to remove const or volatile qualifiers. 
  • Use reinterpret_cast when 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 the C++ Core Guidelines on Typecasting

  • 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_cast where class hierarchy navigation is unavoidable.
  • C.147: Use dynamic_cast to a reference type when failure to find the required class is considered an error.
  • C.148: Use dynamic_cast to a pointer type when failure to find the required class is considered a valid alternative.

Have any suggestions, query or wants to say Hi? Feel free to reach out!

dev Data Types Computer memory Data (computing)

Published at DZone with permission of Vishal Chovatiya. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How Developers Can Create “Sticky” Products
  • The Cypress Edge: Next-Level Testing Strategies for React Developers
  • Data Governance Essentials: Glossaries, Catalogs, and Lineage (Part 5)
  • Tech Trends 2024: Highlights on the Current Tech Industry From a Developer

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!