fbpx

Can you change an int into a float? And because an int occupies multiple bytes in memory, can you get access to the individual bytes by themselves? Or what if you have a derived class and you want to refer to it as if it was a base class? And can you go the other way, from base class to derived class? These are the topics I’ll be explaining today.

If you want to use an initializer list to construct an object, you can use it like this:
int health {9.3};

instead of the older way that truncates:
int health = 9.3;

The C-style cast that I recommend you avoid looks like this to cast a long variable to an int variable:
long count = 100;
int shorterCount = (int)count;

And there is the similar function style cast that I also recommend you avoid. It looks almost the same:
long count = 100;
int shorterCount = int(count);

Instead of using a C-style or function style cast, you should instead use either an initializer list or one of the following named casts:
const_cast
static_cast
reinterpret_cast
dynamic_cast

Listen to the full episode or read the full transcript below.

Transcript

There’s quite a bit of difference between casting in C++ and C# so today’s episode will focus on C++.

The compiler will perform conversions for you when it needs to. Some of these are good and expected. Others will leave you wondering, “What were they thinking?” You can also step in and perform your own conversions and this is called casting.

First, let’s take a simple example where you declare a new int variable called health and give it the initial value of 9.3. Your int variable can’t hold 9.3 so the compiler truncates the 9.3 value and your health variable ends up with just the value 9.

If you want to prevent this automatic conversion from a floating point value to an integer value then instead of writing int health = 9.3, you can put the 9.3 in curly braces and then the equals sign becomes optional. You can just write int health, curly brace, 9.3, curly brace. This is called an initializer list and is a really nice and safe way to initialize your variables, including those that require multiple values.

Converting from 9.3 to 9 is a narrowing conversion and loses some of the information. It’s sort of like what happens when you try to squeeze through a narrow opening and lose some skin along the way.

If you’re worried about this happening to you, then you can always create a method that performs the assignment and then compares the result of the assignment back to the original value. If the values don’t match, then you lost some skin. Or you can use the curly brace initializer list which prevents this. The downside of the curly brace initializer list is that some conversions that might have made it through without being narrowed will now cause an error.

Sometimes conversions will lose precision such as when converting from an integer to a float. You should realize that floats especially and even doubles can’t exactly represent all values. Have you ever tried writing out the decimal value of one third? It just keeps going right? 0.33333 etc. Other number systems such as used by computers have problems too. For instance, while we can write one tenth as 0.1 and one hundredth as 0.01, these simple fractions cause problems for computer floating point values just like one third does in decimal. If you’re writing a banking application, you might want to consider a special class to keep track of currency. Otherwise, you might find that your program has problems counting one cent pennies.

This is not a problem with C++ or even C#. It’s just a matter of being aware of how computers represent and work with numbers.

Another type of conversion that the compiler will do for you is called a promotion and this will not lose information. If you have a char value and assign that to an int, then it’s guaranteed to fit in either an int or an unsigned int. But the compiler will also promote bools, signed chars, unsigned chars, short ints, and unsigned short ints to either an int or an unsigned int. And floats can be promoted to doubles.

This just begins to explain all the rules. It’s actually very complicated and some conversions are left up to the compiler writers to figure out how they want to handle it. Fortunately, I’ve found that just being aware of what could happen has gone a long way to keeping my code portable and safe. You can always refer to the language rules if in doubt. Just don’t expect them to be light reading.

Now that you understand how to use initializer lists to construct objects in a safe manner and some of the conversions that the compiler will do for you, let’s talk about why you might need to take a more explicit approach and perform your own casting. You have a lot more power to do this in C++ and whether that’s good or bad depends. I will say that you should look long and hard anytime you need to cast a variable from one type to another because it could signal a bad design.

There’re times when casting actually makes your code easier to write and maintain and other times when you have to cast in order to get the answer you expect. Let’s say that you want to divide two int values 3 and 3. Your answer will be 1. But what if you try dividing 1 by 3. You would expect the answer to be 1/3 or 0.3333 etc. so you plan for this and use a float variable to hold your result. The problem is that you don’t get 1/3. And this isn’t even a floating point precision problem. Your answer is 0 which is nowhere near 1/3. This is because 1 divided by 3 is using integer arithmetic. And integers can only hold whole numbers. Your answer gets rounded down to the nearest whole number which is 0 and then gets converted to a float value and stored in your float variable. But by this time, it’s lost all hope of being accurate.

What you have to do is force the division to use floating point division instead of integer division. And you do this by casting either or both of your ints to a float first. You only need to cast one because you can’t divide an int by a float or a float by an int. And the compiler won’t convert your float value back to an int because that could lose precision. So the compiler takes the only safe path it has and will convert the other int to a float too and then perform the division between two floats. The answer in this case will be the expected 0.3333 and that’s what will get stored in your float variable.

The original way to cast was carried over from the C language and involved putting the type you wanted to convert to in parenthesis before your variable. So if you have a long variable called count that you want to convert to an int, then you’d put int inside parenthesis in front of count. This would cast the long count to an int. You may get part of your number cut off if the original count value was too big to fit in an int.

The C style casts have another form called functional style casting because it looks like you’re calling a method. If we take the previous example of converting a long variable called count into an int, then with a functional cast, you write int and then put the variable named count inside parenthesis. This sort of resembles what it might look like to call a method named int and passing count as the parameter.

There are three problems with both the C style casts and the functional style casts. One is that they’re hard to spot. And by that, I mean, that they blend in with the rest of your code so well that once you start using them, if you find a problem, it’s really hard to find all the places where you made these casts. There’s nothing specific that you can search for.

The second problem is that these casts do too much. They’re not specific enough to allow you to clearly define your intentions.

And the third problem is that they can easily give you unexpected results. You might lose information or take away the compiler’s ability to help protect you from common mistakes.

The good news is that you should never need to use them. Modern C++ gives you better options. Your first option that you should consider before anything else is to use an initializer list to construct the types you want.

If that doesn’t work, then you have four more choices for explicit casts. These four options are called named casts.

#1. const_cast allows you to cast away the constant attribute of a variable. This will let you change that variable’s value. You should be careful with all these casts. But this one is especially dangerous because it allows you to break your promise not to modify a value.

#2. static_cast allows you to convert one value type to a related value type such as from a float to an int, or from an int to an enumeration.

#3. reinterpret_cast gives you full control to tell the compiler that one type is really something else. You can use this to change your hero into a frog. Be careful because if you don’t change that frog back into a hero and try to use it as a frog, then your program is headed for an almost certain crash. This type of cast is sometimes used to change a pointer to some type to a pointer to chars. Once you have a pointer to chars, you have access to the individual bytes that make up the initial type.

#4. dynamic_cast is actually fairly safe because your program will perform runtime checks to make sure that one type really can be converted to another type. You can use this to convert pointers and references from one type to another type as long as the other type really relates to the first type. This is usually used to cast from a base class to a derived class. the compiler will allow you to automatically convert to a base class as long as the base class is unambiguous and not private. But going from a base class to some derived class is not always safe. This cast will perform runtime checks to make sure you can convert to a derived class.