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 —

  1. static_cast
  2. dynamic_cast
  3. const_cast
  4. reinterpret_cast
  5. 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.

The dreaded diamond

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 , then const_cast
  • reinterpret_cast
  • reinterpret_cast, then const_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 —

  1. Overview of casting
  2. static_cast
  3. reinterpret_cast
  4. const_cast
  5. dynamic_cast
Aniket Bhattacharyea

Aniket Bhattacharyea