Topic : Casting in C++
Author : G. Bowden Wise
Page : 1 Next >>
Go to page :


Casting in C++:
Bringing Safety and Smartness to Your Programs
G. Bowden Wise


The new C++ standard is full of powerful additions to the language: templates, run-time type identification (RTTI), namespaces, and exceptions to name a few. Rather than talk about one of these ``major'' extensions, I will discuss one of the minor extensions: the new C++ casting operators.

The C++ draft standard includes the following four casting operators:

static_cast
const_cast
dynamic_cast, and
reinterpret_cast


These new operators are intended to remove some of the holes in the C type system introduced by the old C-style casts.
In this issue of OBJECTIVE VIEW POINT we will learn about casting in general, discuss the problems with the old C-style cast, and take a look at the new C++ casting operators in more detail.


Why Cast?

Casts are used to convert the type of an object, expression, function argument, or return value to that of another type. Some conversions are performed automatically by the compiler without intervention by the programmer. These conversions are called implicit conversions. The standard C++ conversions and user-defined conversions are performed implicitly by the compiler where needed. Other conversions which must be explicitly specified by the programmer and are appropriately called explicit conversions.

Standard conversions are used for integral promotions (e.g., enum to int), integral conversions (e.g., int to unsigned int), floating point conversions (e.g., float to double), floating-integral conversions (e.g., int to float), arithmetic conversions (e.g., converting operands to the type of the widest operand before evaluation), pointer conversions (e.g., derived class pointer to base class pointer), reference conversions (e.g., derived class reference to base class reference), and pointer-to-member conversions (e.g., from pointer to member of a base class to pointer to member of a derived class).

You can provide a user-defined conversion from a class X to a class Y by providing a constructor for Y that takes an X as an argument:

   Y(const X& x)
or by providing a class Y with a conversion operator:
    operator X()

When a type is needed for an expression that cannot be obtained through an implicit conversion or when more than one standard conversion creates an ambiguous situation, the programmer must explicitly specify the target type of the conversion.
In C, an expression, expr, of type S can be cast to another type T in one of the following ways. By using an explicit cast:

   (T) expr
or by using a functional form:
   T(expr)
We will refer to either of these constructs as the old C-style casts.
The old C-style casts have several shortcomings. First, the syntax is the same for every casting operation. This means it is impossible for the compiler (or users) to tell the intended purpose of the cast. Is it a cast from a base class pointer to a derived class pointer? Does the cast remove the ``const-ness'' of the object? Or, is it a conversion of one type to a completely unrelated type? The truth is, it is impossible to tell from the syntax. As a result, this makes the cast harder to comprehend, not only by humans, but also by compilers which are unable to detect improper casts.

Another problem is that the C-style casts are hard to find. Parentheses with an identifier between them are used all over C++ programs. There is no easy way to ``grep'' a source file and get a list of all the casts being performed.

Perhaps the most serious problem with the old C-style cast is that it allows you to cast practically any type to any other type. Improper use of casts can lead to disastrous results. The old C-style casts have created a few holes in the C type system and have also been a souce of confusion for both programmers and compilers. Even in C++, the old C-style casts are retained for backwards compatibility. However, using the new C++ style casting operators will make your programs more readable, less error-prone and type-safe, and easier to maintain.


The New C++ Casting Operators

The new C++ casting operators are intended to provide a solution to the shortcomings of the old C-style casts by providing:

Improved syntax. Casts have a clear, concise, although somewhat cumbersome syntax. This makes casts easier to understand, find, and maintain.
Improved semantics. The intended meaning of a cast is no longer ambiguous. Knowing what the programmer intended the cast to do makes it possible for compilers to detect improper casting operations.
Type-safe conversions. Allow some casts to be performed safely at run-time. This will enable programmers to check whether a particular cast is successful or not.
C++ introduces four new casting operators:
static_cast, to convert one type to another type;
const_cast, to cast away the ``const-ness'' or ``volatile-ness'' of a type;
dynamic_cast, for safe navigation of an inheritance hierarchy; and
reinterpret_cast, to perform type conversions on un-related types.
All of the casting operators have the same syntax and are used in a manner similar to templates. For example, to perform a static_cast of ptr to a type T we write:

   T* t = static_cast<T> (ptr);

As we will soon see, static_cast is the most general and is intended as a replacement for most C-style casts. The other three forms are for specific circumstances to be discussed below.

The static_cast Operator
The static_cast operator takes the form

    static_cast<T> (expr)

to convert the expression expr to type T. Such conversions rely on static (compile-time) type information.
Subject to certain restrictions, you may use static_cast to convert a base class pointer to a derived class pointer, perform arithmetic conversions, convert an int to an enum, convert a reference of type X& to another reference of type Y&, convert an object of type X to an object of type Y, and convert a pointer-to-member to another pointer-to-member within the same class hierarchy.

Internally, static_casts are used by the compiler to perform implicit type conversions such as the standard conversions and user-defined conversions. In general, a complete type can be converted to another type so long as some conversion sequence is provided by the language.

The downcast of a base class pointer X* to a derived class pointer Y* can be done statically only if the conversion is unambiguous and X is not a virtual base class. Consider this class hierarchy:

   class BankAcct    
         { /* ... */ }
   class SavingsAcct : public BankAcct
         { /* ... */ }
Given a base class pointer, we can cast it to a derived class pointer:
   void f (BankAcct* acct)
   {
      SavingsAcct* d1 =
        static_cast<SavingsAcct*>(acct);
   }


This is called a downcast. The static_cast operator allows you to perform safe downcasts for non-polymorphic classes.
Note that static_cast relies on static (compile-time) type information and does not perform any run-time type checking. This means that if acct does, in fact, not refer to an actual SavingsAcct the result of the cast is undefined. Borland C++ 4.5, seemingly incorrectly, still performs the conversion, however, your compiler mileage may vary. If you want to use run-time type information during conversion of polymorphic class types, use dynamic_cast. It is not possible to perform a downcast from a virtual base class using a static_cast; you must use a dynamic_cast.

More generally a static_cast may be used to perform the explicit inverse of the implicit standard conversions. A conversion from type S to T can only be done if the conversion from type T to S is an implicit conversion. Also, the the ``const-ness'' of the original type, S, must be preserved. You cannot use static_cast to change ``const-ness''; use const_cast instead.

One of the more common uses of static_cast is to perform arithmetic conversions, such as from int to double. For example, to avoid the truncation in the following computation:

   int   total = 500;
   int   days  = 9;
   double rate = total/days;
We can write:
   double rate =
          static_cast<double>(total)/days;

A static_cast may also be used to convert an integral type to an enumeration. Consider:

   enum fruit {apple=0,orange,banana};
   int i  1 = 2;
   fruit f1 = static_cast<fruit> (i1);


The conversion results in an enumeration with the same value as the integral type provided the integral value is within the range of the enumeration. The conversion of an integral value that is not within the range of the enumeration is undefined.
You may also use static_cast to convert any expression to a void in which case the value of the expression is discarded.

One interesting side effect of the old C-style

Page : 1 Next >>