Department of Engineering

IT Services

Casting in C++ (intermediate level)

Casting isn't usually necessary in student-level C++ code, but understanding why it's needed and the restrictions involved can help widen one's understanding of C++. This document uses some basic knowledge about inheritance, pointers and casting to explore casting without going too deeply into the mechanics of C++ (see the Notes section for further information). If you think you already know the basics of inheritance and casting (C++ casting, not old C-style casting), skip to the next section.

Basic inheritance and casting

Inheritance

C++ lets you invent new types of variables. Here's a simple example

class base { 
  public: 
  int i; 
};

This created a new type of variable called base. You can create a new variable of that type much as you can create a variable of a standard type -

base b;

will create a new variable b. Inside this variable there's an integer called b.i.

You can also built new variable types by extending existing ones. The following code creates a variable called d that contains an integer i (inherited from base) and a float called f

class derived: public base { 
   public: 
   float f; 
};

derived d;

You can build a family tree of related types if you want.

Casting

C++ lets you convert between types. This is called casting. Sometimes this happens automatically. For example,

int i=7;
float f=i;

works without fuss because implicit casting happens. It also lets you do

float f=7.7;
int i=f;

even though an integer can't store the value 7.7.

Sometimes C++ will only let you cast if you explicitly request it. C++ has 4 casting operators - const_cast, static_cast, dynamic_cast, and reinterpret_cast. Some of these will be mentioned later.

Sometimes none of the 4 operators will work, because C++ can't determine how the casting should be done. In such cases, you need to write a routine. That will also be covered later.

Casting between user-defined types

Implicit casting can happen between standard types (like float, double, etc). It can also happen between related user-defined types. The following code (that uses the new types invented earlier) compiles without protest.

class base { 
  public: 
  int i; 
};

class derived: public base { 
   public: 
   float f; 
};

int main () {
  base b;
  derived d;

  b=d;
}

d has 2 fields, b only has one, but C++ ignores the extra field, rather in the way that it ignores the decimal places in the earlier example when an integer was made from a float.

Suppose in this example we had d=b; instead of b=d;. What would happen? You might think that d.i=b.i happens, and that d.f is unchanged, or set to 0, but in fact the compiler doesn't allow the cast.

It's worth noticing that C++ only allowed b=d; in the first place because the 2 types involved were related. In the following example the 2nd new type isn't derived from the 1st, though it has exactly the same components as the derived type in the previous example. It doesn't compile.

class base { 
  public: 
  int i; 
};

class base2 { 
  public: 
  int i;
  float f; 
};

int main () {
  base b;
  base2 d;

  b=d;
}

Let's try something else. Suppose we create 2 types derived from the same base type that have exactly the same components.

class base { 
  public: 
  int i; 
};

class child1: public base { 
  public: 
  float f;
};

class child2: public base { 
  public: 
  float f;
};

int main () {
  child1 c1;
  child2 c2;

  c1=c2;
}

Can these siblings work together? No, the code doesn't compile. So as you can see, C++ is cautious about casting.

The casting operators

C++'s casting operators can be used to force the issue when C++ is uncertain about the user's intentions, but the command can only work if C++ already knows what should be done. For now, let's focus on just 2 of the operators

  • static_cast - Such conversions rely on static (compile-time) type information. More generally, a static_cast may be used to perform the explicit inverse of the implicit standard conversions.
  • reinterpret_cast - to perform type conversions on unrelated types.You should use this type of cast only when absolutely necessary.

Pointers and casting

Pointers can be cast too. Here's an example

class base { 
  public: 
  int i; 
};

class derived: public base { 
   public: 
   float f; 
};

int main () {
  base b;
  derived d;

  base *pointer_to_base;
  derived *pointer_to_derived;
  pointer_to_base=&d; // OK. Note that you can't access d.f via
  // pointer_to_base though.
  
  // pointer_to_derived=&b; // Not ok. Were it allowed, then
  // pointer_to_derived->f=7; would cause trouble, because b
  // doesn't have an f field. But you can force the issue.
  pointer_to_derived=static_cast<derived*>(&b); // Allowed
}

If you wanted to store the pointer's value in an integer, the following wouldn't work

class base { 
  public: 
  int i; 
};

int main () {
  base b;
  int i;
  i=&b;
}

You can't use i=static_cast<int>(&b); either, because the types aren't similar enough. You need to use i=reinterpret_cast<int>(&b);, but the reinterpret_cast should be used with care because it can lead to dangerous situations, as this code fragment illustrates

...
int main () {
  base *pointer_to_base;
  int i=-1;
  pointer_to_base=reinterpret_cast<base*>(i);
}

The compiler lets this happen, but pointer_to_base won't point to a valid location for an object on today's machines.

Writing member functions

Classes can not only contain variables, they can contain functions too. Conversion routines are ideal candidates for inclusion within a class. Here the derived class is being extended so that it can deal with the problems mentioned above. An extra constructor (a copy constructor) is added so that a derived object can be created as a copy of a base object, the derived object's f field being initialised to 0.

class base { 
  public: 
  int i; 
};

class derived: public base { 
   public: 
   float f; 
   derived(const base& b) { 
     i=b.i;
     f=0;
   }; 
};

int main () {
  base b;
  derived d=b;
}

Notes

  • downcast - A downcast is a cast from a base class to a class derived from that base class.
  • cross-cast - A cross-cast is a cast between unrelated types
  • Our local FAQ deals with simple uses of casting, and shows why casting is needed at all