News & UpdatesProgrammingWeb programming StoreMy Projects
Links
Affiliates

C++ Tutorial – 27 – Type Conversions

Converting an expression from one type to another is known as type-conversion. This can be done either implicitly or explicitly.

Implicit conversions

An implicit conversion is performed automatically by the compiler when an expression needs to be converted into one of its compatible types. For example, any conversions between the primitive data types can be done implicitly.

long a = 5;   // int implicitly converted to long
double b = a; // long implicitly converted to double

These implicit primitive conversions can be further grouped into two kinds: promotion and demotion. Promotion occurs when an expression gets implicitly converted into a larger type and demotion occurs when converting an expression to a smaller type. Because a demotion can result in the loss of information, these conversions will generate a warning on most compilers. If the potential information loss is intentional, the warning can be suppressed by using an explicit cast.

// Promotion
long   a = 5;  // int promoted to long
double b = a;  // long promoted to double
 
// Demotion
int  c = 10.5; // warning: possible loss of data
bool d = c;    // warning: possible loss of data

Explicit conversions

The first explicit cast is the one inherited from C, commonly called the C-style cast. The desired data type is simply placed in parentheses to the left of the expression that needs to be converted.

int  c = (int)10.5; // double demoted to int
char d = (char)c;   // int demoted to char

C++ casts

The C-style cast is suitable for most conversions between the primitive data types. However, when it comes to conversions between classes and pointers it can be too powerful. In order to get greater control over the different types of conversions possible C++ introduced four new casts, called named casts or new-style casts. These casts are: static, reinterpret, const and dynamic cast.

static_cast<new_type> (expression) 
reinterpret_cast<new_type> (expression) 
const_cast<new_type> (expression) 
dynamic_cast<new_type> (expression)

As seen above, their format is to follow the cast’s name with the new type enclosed in angle brackets and thereafter the expression to be converted in parentheses. These casts allow more precise control over how a conversion should be performed, which in turn makes it easier for the compiler to catch conversion errors. In contrast, the C-style cast includes the static, reinterpret and const cast in one operation. That cast is therefore more likely to execute subtle conversion errors if used incorrectly.

Static cast

The static cast performs conversions between compatible types. It is similar to the C-style cast, but is more restrictive. For example, the C-style cast would allow an integer pointer to point to a char.

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

Since this results in a 4-byte pointer pointing to 1 byte of allocated memory, writing to this pointer will either cause a run-time error or will overwrite some adjacent memory.

*p = 5; // run-time error: stack corruption

In contrast to the C-style cast, the static cast will allow the compiler to check that the pointer and pointee data types are compatible, which allows the programmer to catch this incorrect pointer assignment during compilation.

int *q = static_cast<int*>(&c); // compile-time error

Reinterpret cast

To force the pointer conversion, in the same way as the C-style cast does in the background, the reinterpret cast would be used instead.

int *r = reinterpret_cast<int*>(&c); // forced conversion

This cast handles conversions between certain unrelated types, such as from one pointer type to another incompatible pointer type. It will simply perform a binary copy of the data without altering the underlying bit pattern. Note that the result of such a low-level operation is system-specific and therefore not portable. It should be used with caution if it cannot be avoided altogether.

Const cast

The third C++ cast is the const cast. This one is primarily used to add or remove the const modifier of a variable.

const int myConst = 5;
int *nonConst = const_cast<int*>(&myConst); // removes const

Although const cast allows the value of a constant to be changed, doing so is still invalid code that may cause a run-time error. This could occur for example if the constant was located in a section of read-only memory.

*nonConst = 10; // potential run-time error

Const cast is instead used mainly when there is a function that takes a non-constant pointer argument, even though it does not modify the pointee.

void print(int *p) { std::cout << *p; }

The function can then be passed a constant variable by using a const cast.

print(&myConst); // error: cannot convert 
                 // const int* to int*
 
print(nonConst); // allowed

C-style and new-style casts

Keep in mind that the C-style cast can also remove the const modifier, but again since it does this conversion behind the scenes the C++ casts are preferable. Another reason to use the C++ casts is that they are easier to find in the source code then the C-style cast. This is important because casting errors can be difficult to discover. A third reason for using the C++ casts is that they are unpleasant to write. Since explicit conversion in many cases can be avoided, this was done intentionally so that programmers would look for a different solution.

Dynamic cast

The fourth and final C++ cast is the dynamic cast. This one is only used to convert object pointers and object references into other pointer or reference types in the inheritance hierarchy. It is the only cast that makes sure that the object pointed to can be converted, by performing a run-time check that the pointer refers to a complete object of the destination type. For this run-time check to be possible the object must be polymorphic. That is, the class must define or inherit at least one virtual function. This is because the compiler will only generate the needed run-time type information for such objects.

Dynamic cast examples

In the example below, a MyChild pointer is converted into a MyBase pointer using a dynamic cast. This derived-to-base conversion succeeds, because the Child object includes a complete Base object.

class MyBase { public: virtual void test() {} };
class MyChild : public MyBase {};
 
int main()
{
  MyChild *child = new MyChild();
  MyBase  *base = dynamic_cast<MyBase*>(child); // ok
}

The next example attempts to convert a MyBase pointer to a MyChild pointer. Since the Base object does not contain a complete Child object this pointer conversion will fail. To indicate this, the dynamic cast returns a null pointer. This gives a convenient way to check whether or not a conversion has succeeded during run-time.

MyBase  *base = new MyBase();
MyChild *child = dynamic_cast<MyChild*>(base);
 
if (child == 0) std::cout << "Null pointer returned";

If a reference is converted instead of a pointer, the dynamic cast will then fail by throwing a bad_cast exception. This needs to be handled using a try-catch statement.

#include <exception>
// …  
try { MyChild &child = dynamic_cast<MyChild&>(*base); }
catch(std::bad_cast &e) 
{ 
  std::cout << e.what(); // bad dynamic_cast
}

Dynamic or static cast

The advantage of using a dynamic cast is that it allows the programmer to check whether or not a conversion has succeeded during run-time. The disadvantage is that there is a performance overhead associated with doing this check. For this reason using a static cast would have been preferable in the first example, because a derived-to-base conversion will never fail.

MyBase *base = static_cast<MyBase*>(child); // ok

However, in the second example the conversion may either succeed or fail. It will fail if the MyBase object contains a MyBase instance and it will succeed if it contains a MyChild instance. In some situations this may not be known until run-time. When this is the case dynamic cast is a better choice than static cast.

// Succeeds for a MyChild object
MyChild *child = dynamic_cast<MyChild*>(base);

If the base-to-derived conversion had been performed using a static cast instead of a dynamic cast the conversion would not have failed. It would have returned a pointer that referred to an incomplete object. Dereferencing such a pointer can lead to run-time errors.

// Allowed, but invalid
MyChild *child = static_cast<MyChild*>(base);
 
// Incomplete MyChild object dereferenced
(*child);
Recommended additional reading:
Sams - Teach Yourself C++ in One Hour a Day