C++ Casting, or: "Oh No, They Broke Malloc!"

The first time you try compiling your C code with a C++ compiler can be terrifying. One of the most troublesome offenders is malloc, and you will have your eyes opened to the number of implicit type conversions in your code.

malloc: C vs C++

Consider the following:

int * p = malloc(10);

Recall that malloc is defined to return a void *. The C compiler will happily convert the void * result into whatever type you require. Lines of code like this are likely liberally scattered throughout your repositories.

The C++ compiler is not as kind. Unlike C, the C++ compiler allows implicit conversions TO a void * type, but to convert FROM a void * type requries an explicit cast.

error: cannot initialize a variable of type 'int *' with an rvalue
      of type 'void *'
        int * p = malloc(10);
              ^   ~~~~~~~~~~
1 error generated.

So what can we do about this?

The first thing that likely comes to your mind is our friend, the C-style cast:

int * p = (int*)malloc(10);

This will work, but it is not recommended in C++: there are more explicit ways to cast and describe what we are actually intending.

C++ Casts

C++ provides a variety of ways to cast between types:

  • static_cast
  • reinterpret_cast
  • const_cast
  • dynamic_cast
  • C-style casts

Each of the C++ casts is used in the following generic form:

cast_name<cast_to_type>(item_to_cast)

Let's look at what each of these casts do.

static_cast

static_cast is the main workhorse in our C++ casting world. static_cast handles implicit conversions between types (e.g. integral type conversion, any pointer type to void*). static_cast can also call explicit conversion functions.

int * y = static_cast<int*>(malloc(10));

We will primarily use it for converting in places where implicit conversions fail, such as malloc.

There are no runtime checks performed for static_cast conversions.

static_cast cannot cast away const or volatile.

reinterpret_cast

reinterpret_cast is a compiler directive which tells the compiler to treat the current type as a new type.

You can use reinterpret_cast to cast any pointer or integral type to any other pointer or integral type. This can lead to dangerous situations: nothing will stop you from converting an int to a std::string *.

There are some occasions where reinterpret_cast will likely be required in your embedded systems, such as converting between uintptr_t and an actual pointer:

error: static_cast from 'int *' to 'uintptr_t'
      (aka 'unsigned long') is not allowed
        uintptr_t ptr = static_cast<uintptr_t>(p);
                        ^~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Instead, use this:

uintptr_t ptr = reinterpret_cast<uintptr_t>(p);

reinterpret_cast cannot cast away const or volatile.

const_cast

const_cast is used to add or remove const from a variable. Strangely enough, it can also be used to add or remove volatile from a variable. No other C++ cast can add or remove these keywords.

Use this cast carefully. If the original variable was declared as const, it is not safe to remove const and start modifying the underlying data.

const_cast should primarily be used to add const to a variable (such as for a function overload to use a const version). If you need to remove const from a variable, I recommend stopping and thinking about why you are in this situation. Design changes are likely warranted.

const_cast and volatile

Removing volatile from a keyword is definitely more common in embedded systems than removing const. Often, you will want the variable to be declared as volatile in the context where it is being updated. If you are using the volatile variable as a function input, this can cause problems.

Consider the following contrived example:

void test(int * x)
{
    //do something
}

int main(void)
{
    volatile int p = 0;
    test(&p);

    return 0;
}

In C, this example would compile. However, C++ throws an error:

test.c:14:2: error: no matching function for call to 'test'
        test(&p);
        ^~~~
test.c:5:6: note: candidate function not viable: 1st argument ('volatile int *')
      would lose volatile qualifier
void test(int * x)
     ^
1 error generated.

To prevent this error, we will need to use const_cast:

test(const_cast<int*>(&p));

dynamic_cast

I have never used dynamic_cast in my embedded projects, and I usually keep run-time-type-information (RTTI) disabled. I will provide a cursory overview, but please see "Further Reading" below for more detailed information.

dynamic_cast is used to handle polymorphism. dynamic_cast can be used to convert pointers and references to any polymorphic type at run-time. dynamic_cast is generally used for casting down a type's inheritance hierarchy. If dynamic_cast can't find the desired type in the inheritance hierarchy, it will return nullptr for pointers or throw a std::bad_cast exception for references.

C-style Casts

A C-style cast in C++ tries the following casts in order, using the first C++ cast that works:

  1. const_cast
  2. static_cast
  3. static_cast, then const_cast (change type + remove const)
  4. reinterpret_cast
  5. reinterpret_cast, then const_cast (chagne type + remove const)

Note that dynamic_cast is never considered when using a C-style cast.

Casting Recommendations

Five casts is a lot to keep in mind! Here are some quick rules of thumb for these new casts:

  1. Use static_cast for your ordinary conversions
  2. Use reinterpret_cast for specific cases were you need to reinterpret underlying data (e.g. converting from a pointer to uintptr_t)
  3. Use dynamic_cast for converting pointers and references along an inheritance hierarchy. This should be used only on classes with virtual members
  4. Use const_cast when you need to remove const or volatile keywords. Think carefully before using this cast.
  5. Avoid C-style casts. Be explicit in your intentions.

One unappreciated benefit of using C++-style casts is you can search through your code and immediately locate all casts. With C-style casts, you cannot easily tell the difference between a declaration and a cast if you are performing a search.

Updating Our malloc Calls

That was quite the long detour!

Coming back to our malloc example, we can now see that we should use a static_cast:

int * p = static_cast<int*>(malloc(10));

But all that casting really is a nuisance, isn't it?

Note: When moving forward with C++, remember to use new instead of malloc where possible. This is a typesafe call that will not require a cast.

Further Reading

If you find C++ casting still to be unclear, try the following two links:

These are useful references if you need to refer to API documentation: