C++ Programming

Type ConversionEdit

Type conversion (often a result of type casting) refers to changing an entity of one data type, expression, function argument, or return value into another. This is done to take advantage of certain features of type hierarchies. For instance, values from a more limited set, such as integers, can be stored in a more compact format and later converted to a different format enabling operations not previously possible, such as division with several decimal places' worth of accuracy. In the object-oriented programming paradigm, type conversion allows programs also to treat objects of one type as one of another. One must do it carefully as type casting can lead to loss of data.

Note:
The Wikipedia article about strongly typed suggests that there is not enough consensus on the term "strongly typed" to use it safely. So you should re-check the intended meaning carefully, the above statement is what C++ programmers refer as strongly typed in the language scope.

Automatic type conversionEdit

Automatic type conversion (or standard conversion) happens whenever the compiler expects data of a particular type, but the data is given as a different type, leading to an automatic conversion by the compiler without an explicit indication by the programmer.

Note:
This is not "casting" or explicit type conversions. There is no such thing as an "automatic cast".

When an expression requires a given type that cannot be obtained through an implicit conversion or if more than one standard conversion creates an ambiguous situation, the programmer must explicitly specify the target type of the conversion. If the conversion is impossible it will result in an error or warning at compile time. Warnings may vary depending on the compiler used or compiler options.

This type of conversion is useful and relied upon to perform integral promotions, integral conversions, floating point conversions, floating-integral conversions, arithmetic conversions, pointer conversions.

int a = 5.6;
float b = 7;

In the example above, in the first case an expression of type float is given and automatically interpreted as an integer. In the second case (more subtle), an integer is given and automatically interpreted as a float.

There are two types of automatic type conversions between numeric types: promotion and conversion. Numeric promotion causes a simple type conversion whenever a value is used, while more complex numeric conversions can take place if the context of the expression requires it.

Any automatic type conversion is an implicit conversion if not done explicitly in the source code.

Automatic type conversions (implicit conversions) can also occur in the implicit "decay" from an array to a corresponding pointer type based or as a user defined behavior. We will cover that after we introduce classes (user defined types) as the automatic type conversions of references (derived class reference to base class reference) and pointer-to-member (from pointing to member of a base class to pointing to member of a derived class).

PromotionEdit

A numeric promotion is the conversion of a value to a type with a wider range that happens whenever a value of a narrower type is used. Values of integral types narrower than int (char, signed char, unsigned char, short int and unsigned short) will be promoted to int if possible, or unsigned int if int can't represent all the values of the source type. Values of bool type will also be converted to int, and in particular true will get promoted to 1 and false to 0.

// promoting short to int
short left = 12;
short right = 23;
 
short total = left + right;

In the code above, the values of left and right are both of type short and could be added and assigned as such. However, in C++ they will each be promoted to int before being added, and the result converted back to short afterwards. The reason for this is that the int type is designed to be the most natural integer representation on the machine architecture, so requiring that the compiler do its calculations with smaller types may cause an unnecessary performance hit.

Since the C++ standard guarantees only the minimum sizes of the data types, the sizes of the types commonly vary between one architecture and another (and may even vary between one compiler and another). This is the reason why the compiler is allowed the flexibility to promote to int or unsigned int as necessary.

Promotion works in a similar way on floating-point values: a float value will be promoted to a double value, leaving the value unchanged.

Since promotion happens in cases where the expression does not require type conversion in order to be compiled, it can cause unexpected effects, for example in overload resolution:

void do_something(short arg)
{
    cout << "Doing something with a short" << endl;
}
 
void do_something(int arg)
{
    cout << "Doing something with an int" << endl;
}
 
int main(int argc, char **argv)
{
    short val = 12;
 
    do_something(val); // Prints "Doing something with a short"
    do_something(val * val); // Prints "Doing something with an int"
}

Since val is a short, you might expect that the expression val * val would also be a short, but in fact val is promoted to int, and the int overload is selected.

Numeric conversionEdit

After any numeric promotion has been applied, the value can then be converted to another numeric type if required, subject to various constraints.

Note:
The standard guarantees that some conversions are possible without specifying what the exact result will be. This means that certain conversions that are legal can unexpectedly give different results using different compilers.

A value of any integer type can be converted to any other integer type, and a value of an enumeration type can be converted to an integer type. This only gets complicated when overflow is possible, as in the case where you convert from a larger type to a smaller type. In the case of conversion to an unsigned type, overflow works in a nice predictable way: the result is the smallest unsigned integer congruent to the value being converted (modulo 2^n, where n is the number of bits in the destination type).

When converting to a signed integer type where overflow is possible, the result of the conversion depends on the compiler. Most modern compilers will generate a warning if a conversion occurs where overflow could happen. Should the loss of information be intended, the programmer may do explicit type casting to suppress the warning; bit masking may be a superior alternative.

Floating-point types can be converted between each other, but are even more prone to platform-dependence. If the value being converted can be represented exactly in the new type then the exact conversion will happen. Otherwise, if there are two values possible in the destination type and the source value lies between them, then one of the two values will be chosen. In all other cases the result is implementation-defined.

Floating-point types can be converted to integer types, with the fractional part being discarded.

double a = 12.5;
int b = a;
 
cout << b; // Prints "12"

Note:
If a floating-point value is converted to an integer and the result can't be expressed in the destination type, behavior is undefined by the C++ standard, meaning that your program may crash.

A value of an integer type can be converted to a floating point type. The result is exact if possible, otherwise it is the next lowest or next highest representable value (depending on the compiler).

Explicit type conversion (casting)Edit

Explicit type conversion (casting) is the use of direct and specific notation in the source code to request a conversion or to specify a member from an overloaded class. There are cases where no automatic type conversion can occur or where the compiler is unsure about what type to convert to, those cases require explicit instructions from the programmer or will result in error.

Specific type castsEdit

A set of casting operators have been introduced into the C++ language to address the shortcomings of the old C-style casts, maintained for compatibility purposes. Bringing with them a clearer syntax, improved semantics and type-safe conversions.

All of the casting operators share a similar syntax and as we will see are used in a manner similar to templates, with these new keywords casting becomes easier to understand, find, and maintain.

The basic form of type cast

The basic explicit form of typecasting is the static cast.

A static cast looks like this:

static_cast<target type>(expression)

The compiler will try its best to interpret the expression as if it would be of type type. This type of cast will not produce a warning, even if the type is demoted.

int a = static_cast<int>(7.5);

The cast can be used to suppress the warning as shown above. static_cast cannot do all conversions; for example, it cannot remove const qualifiers, and it cannot perform "cross-casts" within a class hierarchy. It can be used to perform most numeric conversions, including conversion from a integral value to an enumerated type.

static_castEdit

The static_cast keyword can be used for any normal conversion between types. Conversions that rely on static (compile-time) type information. This includes any casts between numeric types, casts of pointers and references up the hierarchy, conversions with unary constructor, conversions with conversion operator. For conversions between numeric types no runtime checks if data fits the new type is performed. Conversion with unary constructor would be performed even if it is declared as explicit.

Syntax
    TYPE static_cast<TYPE> (object);

It can also cast pointers or references down and across the hierarchy as long as such conversion is available and unambiguous. For example, it can cast void* to the appropriate pointer type or vice-versa. No runtime checks are performed.

BaseClass* a = new DerivedClass();
static_cast<DerivedClass*>(a)->derivedClassMethod();
Common usage of type casting

Performing arithmetical operations with varying types of data type without an explicit cast means that the compiler has to perform an implicit cast to ensure that the values it uses in the calculation are of the same type. Usually, this means that the compiler will convert all of the values to the type of the value with the highest precision.

The following is an integer division and so a value of 2 is returned.

float a = 5 / 2;

To get the intended behavior, you would either need to cast one or both of the constants to a float.

float a = static_cast<float>(5) / static_cast<float>(2);

Or, you would have to define one or both of the constants as a float.

float a = 5f / 2f;


const_castEdit

The const_cast keyword can be used to remove the const or volatile property from an object. The target data type must be the same as the source type, except (of course) that the target type doesn't have to have the same const qualifier. The type TYPE must be a pointer or reference type.

Syntax
    TYPE* const_cast<TYPE*> (object);
    TYPE& const_cast<TYPE&> (object);

For example, the following code uses const_cast to remove the const qualifier from a object:

class Foo {
public:
  void func() {} // a non-const member function
};
 
void someFunction( const Foo& f )  {
  f.func();      // compile error: cannot call a non-const 
                 // function on a const reference 
  Foo &fRef = const_cast<Foo&>(f);
  fRef.func();   // okay
}


dynamic_castEdit

The dynamic_cast keyword is used to casts a datum from one pointer or reference of a polymorphic type to another, similar to static_cast but performing a type safety check at runtime to ensure the validity of the cast. Generally for the purpose of casting a pointer or reference up the inheritance chain (inheritance hierarchy) in a safe way, including performing so-called cross casts.

Syntax
    TYPE& dynamic_cast<TYPE&> (object);
    TYPE* dynamic_cast<TYPE*> (object);

The target type must be a pointer or reference type, and the expression must evaluate to a pointer or reference.

If you attempt to cast to a pointer type, and that type is not an actual type of the argument object, then the result of the cast will be NULL.

If you attempt to cast to a reference type, and that type is not an actual type of the argument object, then the cast will throw a std::bad_cast exception.

When it doesn't fail, dynamic cast returns a pointer or reference of the target type to the object to which expression referred.

  struct A {
    virtual void f() { }
  };
  struct B : public A { };
  struct C { };
 
  void f () {
    A a;
    B b;
 
    A* ap = &b;
    B* b1 = dynamic_cast<B*> (&a);  // NULL, because 'a' is not a 'B'
    B* b2 = dynamic_cast<B*> (ap);  // 'b'
    C* c = dynamic_cast<C*> (ap);   // NULL.
 
    A& ar = dynamic_cast<A&> (*ap); // Ok.
    B& br = dynamic_cast<B&> (*ap); // Ok.
    C& cr = dynamic_cast<C&> (*ap); // std::bad_cast
  }


reinterpret_castEdit

The reinterpret_cast keyword is used to simply cast one type bitwise to another. Any pointer or integral type can be cast to any other with reinterpret cast, easily allowing for misuse. For instance, with reinterpret cast one might, unsafely, cast an integer pointer to a string pointer. It should be used to cast between incompatible pointer types.

Syntax
    TYPE reinterpret_cast<TYPE> (object);

The reinterpret_cast<>() is used for all non portable casting operations. This makes it simpler to find these non portable casts when porting an application from one OS to another.

The reinterpret_cast<T>() will change the type of an expression without altering its underlying bit pattern. This is useful to cast pointers of a particular type into a void* and subsequently back to the original type.

int a = 0xffe38024;
int* b = reinterpret_cast<int*>(a);


Old C-style castsEdit

Other common type casts exist, they are of the form type(expression) (a functional, or function-style, cast) or (type)expression (often known simply as a C-style cast). The format of (type)expression is more common in C (where it is the only cast notation). It has the basic form:

int i = 10;
long l;
 
l = (long)i; //C programming style cast
l = long(i); //C programming style cast in functional form (preferred by some C++ programmers) 
             //note: initializes a new long to i, this is not an explicit cast as in the example above
             //however an implicit cast does occur. i = long((long)i);

A C-style cast can, in a single line of source code, make two conversions. For instance remove a variable consteness and alter its type. In C++, the old C-style casts are retained for backwards compatibility.

const char string[]="1234";
function( (unsigned char*) string ); //remove const, add unsigned

There are several shortcomings in the old C-style casts:

  1. They allows casting practically any type to any other type. Leading to lots of unnecessary trouble, even to creating source code that will compile but not to the intended result.
  2. The syntax is the same for every casting operation. Making it impossible for the compiler and users to tell the intended purpose of the cast.
  3. Hard to identify in the source code.

The C++ specific cast keyword are more controlled. Some will make the code safer since they will enable to catch more errors at compile-time, and all are easier to search, identify and maintain in the source code. Performance wise they are the same with the exception of dynamic_cast, for which there is no C equivalent. This makes them generally preferred.

Last modified on 23 May 2011, at 18:21