Global training solutions for engineers creating the world's electronics

Guidelines for avoiding C++ casting issues

Introduction

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

When calling subroutines or assigning results from returns, the situation arises in which types don't match, and strict C++ typing appears to get in the way. Several solutions to this problem include implicit conversions, explicit conversions, and casting. Some of these are more dangerous than others. This article clarifies and guides you to make good decisions.

Some general comments are appropriate before proceeding.

First, good C++ programmers avoid casting whenever possible. Casting often has terrible results, including runtime overhead and incorrect conversions, unless adequately understood.

Second, these same programmers rightfully criticize any casting they see during code reviews unless it is justified adequately with comment blocks.

Third, be afraid whenever you see or feel the need for casting. Usually, there are better alternatives.

Conversions

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

1
2
3
4

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:

xxxxxxxxxx
1
1

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

xxxxxxxxxx
4
1
2
3
4

When creating classes, you should create needed conversions. Single argument constructors act as conversions to a class, which must also 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 explicit keyword. In the following, omit the keyword explicit for operators for C++ before C++11.

x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

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:

xxxxxxxxxx
3
1
2
3

The C++ standard interprets this as applying it's named casts; however, no ordering is specified. The generally accepted order is: const_cast, static_cast, reinterpret_cast, and dynamic_cast. These are discussed in the following sections.

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 if conversion or construction does not work. This type of cast 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, it won't compile. The T is a type name such as int, a class name, or a struct name.

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

In many cases, explicitly stating static_cast<T> isn't necessary. Still, 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 be 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 dynamic_cast. However, it does not do run-time checking, and it is an undefined behavior to static_cast<T> down a hierarchy to a type that isn't the object type. Thus static_cast<> should not be used for downcasting even if on some compilers it appears to work. Use dynamic_cast<> instead (discussed later in this article).

Const cast

const_cast<T> can be used to remove or add const to a variable, and no other C++ cast has this ability (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.

It would be best not to use the const_cast operator to override a constant variable's constant status directly.

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

Guideline: Avoid const_cast if at all possible.

Dynamic cast

dynamic_cast<T> is almost exclusively used for handling polymorphism. You can cast a pointer or a 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 attempt to cast sideways or even up another chain if multiple inheritance is involved. 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 bad reference.

If type-id is not void*, a run-time check is made to see if the object pointed to by the 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 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. Essentially, the only guarantee you get with reinterpret_cast is that you will get the same value if you cast the result back to the original type. There are several 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 it in an aligned pointer's low bits. It is also used for embedded software to convert absolute hardware addresses into pointers, although there are alternatives.

For example:

xxxxxxxxxx
4
1
2
3
4

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 usually defined as the first of the following which succeeds:

xxxxxxxxxx
5
1
2
3
4
5

The C++ standard does not guarantee the above ordering.

Because of the preceding table, C++ coders should never use C-style casting. You should know explicitly which type of cast is needed and be able to justify its use.

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

Guidelines

  • Whenever possible, avoid using any casting. Explicit conversion is preferable.

  • Use C++ uniform initialization syntax and auto whenever possible to avoid implicit conversions.

  • During code reviews, be especially suspicious of casts and require good comments or documentation demonstrating the need. Polymorphism is a reasonable justification.

  • If casting is unavoidable, then justify the decision with well-written comment blocks 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 a 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!

You can find some code, with expanded tests, for this example at https://github.com/Doulos/cpp_casting.

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

  9. C++ Core Guidelines

  10. GSL (Guideline Support Library)


This article was written by David C Black, Senior Member Technical Staff at Doulos. Version 1.7


This article is Copyright (C) 2018-2025 by Doulos. All rights are reserved.