Global training solutions for engineers creating the world's electronics

Introduction to C++ Casting Issues

The purpose of this article is to clear up issues with casting in C++.

There are many times when calling subroutines or assigning results from returns, the situation arises that types don't match, and C++ strict typing appears to get in the way. To solve this problem, there are several solutions including implict, explict conversions, and casting. Some of these are more dangerous than others. This article attempts to clarify and guide you to make good decisions.

Some general comments are appropriate before proceeding.

First, good C++ programmers avoid casting whenever possible. Casting have very bad results unless properly understood including runtime overhead and incorrect conversions.

Second, these same programmers rightfully take a dim view of any casting they see during code reviews unless that casting is properly justified with comment blocks.

Third, whenever you see or feel the need for casting, be afraid.

Conversions

Conversions come in two varieties. Implicit conversions include things like int to float and visa versa.

Unfortunately, implicit conversions can get you in trouble. For instance, char is considered to be an 8-bit integer. Consider the following legal, but highly likely bug:

To avoid this problem, you can use explicit conversions using the target type as an operator function. For instance:

When creating classes, you should create needed conversions. Single argument constructors act as conversions to a class, which also need to be designated as explicit. You can use operator definitions to define conversions to other classes. With modern C++ you should also require single argument operators to be explicit by adding the explict keyword. In the following, omit the keyword explicit for operators for C++ prior to C++11.

Sometimes, a conversion does not exist, but you believe you know more than the compiler. In these situations, it may be appropriate to use a cast. For these situations, C++ supplies four types of casting with various levels of safety.

C-style casts - part 1

C provides a cast of the form (TYPE)VALUE, but this is extremely dangerous! There are two reasons. First, it says, I know more than the compiler. Second, because the syntax is terse (few characters), it is easy to overlook. Consider the following disaster:

Smart C++ programmers NEVER use C-style casts. There is another section on this ahead. Read on…

Static cast

static_cast<T> is the first cast you should attempt to use. It is called static because the C++ standard requires compilers to validate static_casts at compile-time. If the compiler cannot resolve the type conversion as valid, then it won't compile. The T is a type name such as int, a class name or struct.

static_cast<T> does things like implicit conversions between compatible types (such as int to float, or pointer to void*), and it can also call explicit conversion functions (or implicit ones).

In many cases, explicitly stating static_cast<T> isn't necessary, but it's important to note that the T(something) syntax may be equivalent to (T)something (see ISO-IEC-14882-2011 section 5.2.3) and should be avoided (more on that later). A T(something, something_else) (i.e. two or more arguments) is safe, and guaranteed to call a constructor. With C++11 you may also safely use uniform initialization of the form T{something}.

static_cast<T> can also cast through inheritance hierarchies. It is unnecessary when casting upwards (towards a base class), but when casting downwards it can be used as long as it doesn't cast through virtual inheritance, which requires use of dynamic_cast. It does not do run-time checking, however, and it is undefined behavior to static_cast<T> down a hierarchy to a type that isn't actually the type of the object. Thus static_cast<> should not generally be used for down casting.

Const cast

const_cast<T> can be used to remove or add const to a variable, and no other C++ cast can remove it (not even reinterpret_cast). It is important to note that using it is only undefined if the original variable is const; if you use it to take the const off a reference to something that wasn't declared with const, it is safe. For instance, this can be useful when overloading member functions based on const. It can also add const to an object, such as to call a member function overload. Although occasionally good reasons exist to use const_cast<T>, many experts suggest it is dangerous. The danger comes because you can remove the const property from something that should never be changed. This will create undefined behavior.

You should not use the const_cast<T> operator to override a constant variable's constant status directly.

const_cast also works similarly on volatile, though that's less common.

Dynamic cast

dynamic_cast<T> is almost exclusively used for handling polymorphism. You can cast a pointer or reference to any polymorphic type to any other class type (a polymorphic type has at least one virtual function, declared or inherited). You don't have to use it to cast downwards, you can cast sideways or even up another chain. The dynamic_cast will seek out the desired object and return it if possible. If it can't, it will return nullptr in the case of a pointer, or throw std::bad_cast in the case of a reference.

If type-id is not void*, a run-time check is made to see if the object pointed to by expression can be converted to the type pointed to by T.

dynamic_cast has some limitations, though. It doesn't work if you don’t use virtual inheritance and multiple objects of the same type are in the inheritance hierarchy (i.e., the so-called dreaded diamond inheritance). dynamic_castalso can only go through public inheritance - it will always fail to travel through protected or private inheritance. However, this is rarely an issue as such forms of inheritance are rare.

Although dynamic_cast conversions are safer than static_cast, dynamic_cast only works on pointers or references, and the run-time type check is overhead.

Reinterpret cast

reinterpret_cast<T> is the most severe cast, and should be used very sparingly. It turns one type directly into another - such as casting the value from one pointer to another, storing a pointer in an int, or all sorts of other nasty things. Largely, the only guarantee you get with reinterpret_cast is that if you cast the result back to the original type, you will get the exact same value. There are many conversions that reinterpret_cast cannot do, too. It's used primarily for weird conversions and bit manipulations, like turning a raw data stream into actual data or storing data in the low bits of an aligned pointer.

For example:

This is essentially how the fast inverse square root works.

C-style casts - part 2

C-style casts are casts using (type)object. A C-style cast used in C++ is defined as the first of the following which succeeds:

    const_cast<T>
    static_cast<T>
    static_cast, then const_cast<T>
    reinterpret_cast<T>
    reinterpret_cast<T>, then const_cast<T>

Because of the preceding table, C++ coders should never use C-style casting.

C-style casts also ignore access control when performing a static_cast, which means that they have the ability to perform an operation that no other cast can. This is bad; therefore, avoid C-style casts.

Guidelines

  • Whenever possible avoid using any type of casting. Conversion is preferable.
  • Use C++ uniform initialization syntax whenever possible to avoid implicit conversions.
  • During code reviews, be especially suspicious of casts and require documentation demonstrating the need.
  • If casting is unavoidable, then justify the decision with a well written comment block next to every cast or group of casts.
  • Use dynamic_cast for converting pointers/references within an inheritance hierarchy. Runtime overhead is insignificant compared to the bugs it may avert.
  • Use static_cast for ordinary type conversions.
  • Use reinterpret_cast for low-level reinterpretation of bit patterns. Use with extreme caution. This type of casting is often non-portable due to endianess issues.
  • Use const_cast for casting away const/volatile. Avoid this unless you are stuck using a const-incorrect API.
  • Use conversion operators (e.g. operator int()) or constructors (e.g. T2(T1)) when possible, but be sure they are conversions and not devolved C-style casts.
  • Don’t ever use C-style casts!

Download an expanded example

You can download an expanded version of this code here » 

In exchange, we will ask you to enter some personal details. To read about how we use your details, click here. On the registration form, you will be asked whether you want us to send you further information concerning other Doulos products and services in the subject area concerned.

References

  1. Stack Overflow Origin of this article
  2. Microsoft Discussion of static_cast
  3. Wikipedia static_cast
  4. Wikipedia dynamic_cast
  5. Wikipedia const_cast
  6. Wikipedia reinterpret_cast
  7. Wikipedia multiple inheritance
  8. ISOCPP on Multiple inheritance

 


Written by David C Black, Senior Member Technical Staff at Doulos. Version 1.4

This article is Copyright © 2018-2023 by Doulos. All rights are reserved.