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 come in two varieties. Implicit conversions include things like int
to float
and visa versa.
double d1 = 3.14159, d2;
int i1 = 42, i2;
d2 = i1; // implicit conversion
i2 = f1; // implicit conversion
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
int buffer['t']{}; // an array of 116 integers - silently compiles -- bug?
To avoid this problem, you can use explicit conversions using the target type as an operator function. For instance:
xxxxxxxxxx
int i;
float pi(3.14159);
i = int(pi); // explicit conversion
int buffer[int('t')]{}; // only slightly better than previous example
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.
static_assert( __cplusplus >= 2014L, "Requires C++14" );
using std::literals;
class Polar; //< forward declare
class Rect {
public:
using std::string;
Rect(double x, double y) : m_x{x}, m_y{y} {}
Rect() = default; // default constructor
explicit Rect(double x) : Rect{x,0.0} {} // explicit constructor
explicit Rect(const Polar& p); // explicit conversion
explicit operator double() const { return m_x; } // explicit conversion
[[nodiscard]] std::string string() const {
return "R{ "s + std::to_string(m_x) + ", "s + std::to_string(m_y) + " }"s;
}
friend std::ostream& operator<<(std::ostream& os, const Rect& rhs) {
return os << rhs.string();
}
[[nodiscard]] double magnitude() const { return std::sqrt(m_x*m_x+m_y*m_y); }
[[nodiscard]] double angle() const { return std::atan2(m_y,m_x); }
private:
double m_x{}, m_y{};
};
class Polar {
public:
Polar(double r, double a): m_r{r}, m_a{a} {}
Polar() = default; // default constructor
explicit Polar(double r) : Polar{r,0.0} {}
explicit Polar(const Rect& r) : Polar{r.magnitude(), r.angle()} {}
explicit operator double() const { return m_r; } // explicit conversion
[[nodiscard]] double run() const { return m_r * std::sin(m_a); }
[[nodiscard]] double rise() const { return m_r * std::cos(m_a); }
[[nodiscard]] std::string string() const {
return "P{ "s + std::to_string(m_r) + ", "s + std::to_string(m_a) + " }"s;
}
friend std::ostream& operator<<(std::ostream& os, Polar const& rhs) {
return os << rhs.string();
}
private:
double m_r{}, m_a{};
};
Rect::Rect(const Polar& p) : Rect{ p.run(), p.rise() } {}
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 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
char *pc = "HelloWorld";
float *pf;
pf = (float*)pc; //< DANGEROUS cast - do you know the internal format of floats?
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<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_cast
s 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<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
const_cast
also works similarly on volatile
, though that's less common.
Guideline: Avoid const_cast if at all possible.
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_cast
also 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<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
int32_t herp = 1337;
float* derp = reinterpret_cast<float*>(&herp); // dumb idea
float magic = *derp;
auto register = reinterpret_cast<volatile uint32_t* const>(0x1000'2000ul);
This is essentially how the fast inverse square root works.
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
const_cast<T>
static_cast<T>
const_cast<static_cast<T>>
reinterpret_cast<T>
const_cast<reinterpret_cast<T>>
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.
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.
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.