Understanding C++ Casts
C++, being a strongly typed language, is strict with its types. And there are always cases when you need to convert one type into another, which is known as casting. Sometimes, the casting is done implicitly. For example —
int a = 10;
long b = a;
This is known as implicit conversion. More specifically, the above example is of standard conversion, which occurs automatically between fundamental types (int
to short
, int
to float
, int
to bool
etc.) and few pointers.
There can also be implicit casts between classes with constructor or operator conversions. For example —
struct A {};
struct B {
B (A a) {}
};
A a;
B b = a;
Here an implicit conversion happened from A
to B
because B
has a constructor that takes an object of A
as parameter.
But in some cases, the implicit conversion doesn’t work. Cases include where we want to interpret object of one type as another. Depending on the use cases, C++ offers a few weapons —
static_cast
dynamic_cast
const_cast
reinterpret_cast
- C style cast and function style cast
We’ll go over them one by one, and explain each one.
static_cast
static_cast
can be used to convert between pointers to related classes (up or down the inheritance hierarchy). It can also perform implicit conversions.
Consider this example —
class Mammal{};
class Human: public Mammal{};
Human *h = new Human; // Pointer to object of derived type
Mammal *m = static_cast<Mammal *>(h); // cast it to pointer to base type. static_cast here is unnecessary
Mammal *m2 = static_cast<Human *>(m); // cast back to pointer to derived type
When we’re casting up the hierarchy, static_cast
is not needed. Every Human
is a Mammal
. So a Human *
can be converted to a Mammal *
implicitly. static_cast would actually perform this implicit cast if you use it anyway.
But every Mammal
may not be a Human
. So implicit conversion from Mammal *
to Human *
is not allowed. That’s where static_cast
comes in. If we omit the static cast, we’ll get an error something along the line of
invalid conversion from ‘Mammal*’ to ‘Human*’
But, using static_cast
, we can still do the conversion anyway. static_cast
doesn’t perform any checks. So it’s the programmers duty to ensure that the conversion should be valid. An invalid conversion might not fail, but can cause problems later when the pointer points to an incomplete type and is dereferenced. Consider this example —
class Mammal {};
class Human: public Mammal {
public:
virtual void scream() { // Note the virtual
std::cout << "MOM" << std::endl;
}
};
Mammal *m = new Mammal; // Mammal that is not a human!
Human *h = static_cast<Human *>(m); // OK so far
h -> scream(); // Mayhem!!!
And it segfaults!
So, don’t use static_cast
to cast down the hierarchy unless you’re absolutely sure the conversion is valid.
static_cast
won’t let you convert between two unrelated classes —
class A {};
class B {};
A *a = new A;
B *b = static_cast<A *>(a);
Gives an error -
cannot convert 'A*' to 'B*' in initialization
dynamic_cast
dynamic_cast
is related to static_cast
in the sense it helps to cast through inheritance, but it’s more powerful than static_cast
but has an overhead. It performs a check in order to prevent the case above.
Consider this example —
class Mammal{};
class Human: public Mammal{};
Human *h = new Human; // Pointer to object of derived type
Mammal *m = dynamic_cast<Mammal *>(h); // ok
Human *h1 = dynamic_cast<Human *>(m) // Error
The second conversion would produce a compilation error since base-to-derived conversions are not allowed with dynamic_cast
unless the base class is polymorphic (has at least one virtual
function, either declared or through inheritance).
So, let’s make the base polymorphic and see what happens —
class Mammal { public: virtual void scream() {} };
class Human: public Mammal {
public:
void scream() override {
std::cout << "MOM" << std::endl;
}
};
Human *h = new Human;
Mammal *m = dynamic_cast<Mammal *>(h);
Human *h1 = dynamic_cast<Human *>(m);
h1 -> scream();
This works as you’d expect.
What happens if we try to cast a Mammal *
to a Human *
where the Mammal
is not actually a Human
? (that crashed the static_cast
one) —
Mammal *m = new Mammal;
Human *h = dynamic_cast<Human *>(m);
if(h == nullptr) std::cout << "Oops! Cast failed!" << std::endl;
else
h -> scream();
This produces the output Oops! Cast failed
So, as you can see, dynamic_cast
performs a check. It returns nullptr
if you’re trying to convert pointers or throws std::bad_cast
if you’re trying to convert references.
But remember, this check happens in runtime, and not compile time. It requires the Run-Time Type Information (RTTI) to keep track of dynamic types and thus has a slight overhead.
Let’s see now what happens when we try to convert two unrelated classes —
class A{ public: void f() {} };
class B{};
A *a = new A;
B *b = dynamic_cast<A *>(a);
This doesn’t give a compilation error (unlike static_cast
, because the check is performed at runtime (at that point b
will be nullptr
).
Finally, one more thing dynamic_cast
can do is “side cast.” To understand this, consider this classic “dreaded diamond” hierarchy —
struct A {
virtual void f() = 0;
};
struct L: A {
virtual void f() override {
std::cout << "Left" << std::endl;
}
};
struct R: A {
virtual void f() override {
std::cout << "Right" << std::endl;
}
};
struct D: L, R {};
Here, A
is the base class. L
and R
inherit from A
, and D
inherits from L
and R
.
By side casting, we mean to say that we should be able to cast an object of type L
as type R
and it should behave exactly as type R
(and vice versa). This is of course possible only when the underlying object is actually of type D
. static_cast
however, can’t help us here. Consider this code —
D *d = new D; // most derived type
L *l = d; // Left type
R *r = d; // Right type
A *bl = l; // Base class through left
A *br = r; // Base class through right
br -> f(); // prints "Right"
static_cast<L *>(br) -> f(); // still prints "Right"
dynamic_cast<L *>(br) -> f(); // prints "Left"
bl -> f(); // prints "Left";
static_cast<R *>(bl) -> f(); // still prints "Left"
dynamic_cast<R *>(bl) -> f(); // prints "Right"
const_cast
const_cast
is the only cast that can be used to add const
to a type or take const
out of a type. This is useful when, say you want to pass a non const
argument to a function which expects const
arguments.
Consider this function —
void f(int& x) { x = 5; }
It expects a non const
reference to int
.
Now suppose you have something like this —
int i = 4; // non const variable
const int& j = i; // const reference
If you try to call f
with j
as argument, you’ll get an error —
error: binding reference of type 'int&' to 'const int' discards qualifiers
What you can do, is to remove the const
with const_cast
—
f(const_cast<int&>(j));
And it will work.
Note however, that you can remove const
away from an object only if it was actually declared as non const
. Removing const
from a const
object is undefined behaviour —
const int i= 3; // j is declared const
int* p = const_cast<int*>(&i);
*p = 4; // undefined behavior
Similarly, you can also add const
to an object. const_cast
works on volatile keyword too, although that’s rare.
reinterpret_cast
This is the most dangerous cast and should be used with care. Do not use it if you don’t know what you’re doing. This cast converts any type of pointer to any other type of pointer, even unrelated types. No checks are performed. It simply copies the binary data from one pointer to another.
All types of pointer conversions are allowed. You can even cast pointers to and from integer types. The only guarantee is that if you cast a pointer to an integer type that is large enough to hold it back, and then cast it back to pointer, you get a valid pointer.
We’ll not go into much detail about reinterpret_cast
, at least not in this post. You can read here if you’re interested.
C-style and function-style casts
And finally, we have the C-style and function-style casts —
(type) object; // c-style cast
type(object); // function-style cast
These two are functionally equivalent and perform the followings in that order until one succeeds —
const_cast
static_cast
(ignoring access restrictions)static_cast
, thenconst_cast
reinterpret_cast
reinterpret_cast
, thenconst_cast
It’s better not to use these two because of the fact that they can invoke reinterpret_cast
, unless you’re 100% sure static_cast
will succeed. Even then, it’s better to explicitly use static_cast
.
So, now you know about the different types of casting provided by C++. If you want you can read about them in details from the following sources —