Search

6.14 — Implicit type conversion (coercion)

Previously, you learned that a value of a variable is stored as a sequence of bits, and the data type of the variable tells the compiler how to interpret those bits into meaningful values. Different data types may represent the “same” number differently -- for example, the integer value 3 and the float value 3.0 are stored as completely different binary patterns.

So what happens when we do something like this?

In such a case, the compiler can’t just copy the bits representing the int value 3 into float variable f. Instead, it needs to convert the integer value 3 to a floating point number, which can then be assigned to float variable f.

The process of converting a value from one data type to another is called a type conversion. Type conversions can happen in many different cases:

  • When assigning to or initializing a variable with a value of a different data type:

  • When passing a value to a function where the function parameter is of a different data type:

  • When returning a value from a function where the function return type is of a different data type:

  • Using a binary operator with operands of different types:

In all of these cases (and quite a few others), C++ will use type conversion to convert one data type to another data type.

There are two basic types of type conversion: implicit type conversion, where the compiler automatically transforms one data type into another, and explicit type conversion, where the developer uses a casting operator to direct the conversion.

We’ll cover implicit type conversion in this lesson, and explicit type conversion in the next.

Implicit type conversion

Implicit type conversion (also called automatic type conversion or coercion) is performed whenever one data type is expected, but a different data type is supplied. If the compiler can figure out how to do the conversion between the two types, it will. If it doesn’t know how, then it will fail with a compile error.

All of the above examples are cases where implicit type conversion will be used.

There are two basic types of implicit type conversion: promotions and conversions.

Numeric promotion

Whenever a value from one fundamental data type is converted into a value of a larger fundamental data type from the same family, this is called a numeric promotion (or widening, though this term is usually reserved for integers). For example, an int can be widened into a long, or a float promoted into a double:

While the term numeric promotion covers any type of promotion, there are two other terms with specific meanings in C++:

  • Integral promotion involves the conversion of integer types narrower than int (which includes bool, char, unsigned char, signed char, unsigned short, and signed short) to an int (if possible) or an unsigned int (otherwise).
  • Floating point promotion involves the conversion of a float to a double.

Integral promotion and floating point promotion are used in specific cases to convert smaller data types to int/unsigned int or double, because int and double are generally the most performant types to perform operations on.

The important thing to remember about promotions is that they are always safe, and no data loss will result.

For advanced readers

Under the hood, promotions generally involve extending the binary representation of a number (e.g. for integers, adding leading 0s).

Numeric conversions

When we convert a value from a larger type to a similar smaller type, or between different types, this is called a numeric conversion. For example:

Unlike promotions, which are always safe, conversions may or may not result in a loss of data. Because of this, code that causes an implicit conversion to be performed will often cause the compiler to issue a warning.

The rules for conversions are complicated and numerous, so we’ll just cover the common cases here.

In all cases, converting a value into a type that doesn’t have a large enough range to support the value will lead to unexpected results. For example:

In this example, we’ve assigned a large integer to a char (that has range -128 to 127). This causes the char to overflow, and produces an unexpected result:

48

However, converting from a larger integral or floating point type to a smaller similar type will generally work so long as the value fits in the range of the smaller type. For example:

This produces the expected result:

2
0.1234

In the case of floating point values, some rounding may occur due to a loss of precision in the smaller type. For example:

In this case, we see a loss of precision because the float can’t hold as much precision as a double:

0.123456791

Converting from an integer to a floating point number generally works as long as the value fits within the range of the floating type. For example:

This produces the expected result:

10

Converting from a floating point to an integer works as long as the value fits within the range of the integer, but any fractional values are lost. For example:

In this example, the fractional value (.5) is lost, leaving the following result:

3

Conversions that could cause loss of information, e.g. floating point to integer, are called narrowing conversions. Since information loss is generally undesirable, brace initialization doesn’t allow narrowing conversions.

Evaluating arithmetic expressions

When evaluating expressions, the compiler breaks each expression down into individual subexpressions. The arithmetic operators require their operands to be of the same type. To ensure this, the compiler uses the following rules:

  • If an operand is an integer that is narrower than an int, it undergoes integral promotion (as described above) to int or unsigned int.
  • If the operands still do not match, then the compiler finds the highest priority operand and implicitly converts the other operand to match.

The priority of operands is as follows:

  • long double (highest)
  • double
  • float
  • unsigned long long
  • long long
  • unsigned long
  • long
  • unsigned int
  • int (lowest)

We can see the usual arithmetic conversion take place via use of the typeid operator (included in the <typeinfo> header), which can be used to show the resulting type of an expression.

In the following example, we add two shorts:

Because shorts are integers, they undergo integral promotion to ints before being added. The result of adding two ints is an int, as you would expect:

int 9

Note: Your compiler may display something slightly different, as the format of typeid.name() is left up to the compiler.

Let’s take a look at another case:

In this case, the short undergoes integral promotion to an int. However, the int and double still do not match. Since double is higher on the hierarchy of types, the integer 2 gets converted to the double 2.0, and the doubles are added to produce a double result.

double 6.0

This hierarchy can cause some interesting issues. For example, take a look at the following code:

you might expect the expression 5u - 10 to evaluate to -5 since 5 - 10 = -5. But here’s what actually happens:

4294967291

In this case, the signed integer 10 is promoted to an unsigned integer (which has higher priority), and the expression is evaluated as an unsigned int. Since -5 can’t be stored in an unsigned int, the calculation wraps around, and we get an answer we don’t expect.

This is one of many good reasons to avoid unsigned integers in general.

Quiz time

Question #1

What’s the difference between a numeric promotion and a numeric conversion?

Show Solution


6.15 -- Explicit type conversion (casting)
Index
6.13 -- The auto keyword

51 comments to 6.14 — Implicit type conversion (coercion)

  • joel.giedt

    One common type casting that comes up all the time in codes I work with is switching between different types of pointers. The most unavoidable example is something like

    Float *ptr = (Float *)smalloc(num_bytes);

    Because smalloc() is like malloc in that it returns a void type. You might wonder why not use the new operator for dynamic allocation? The reason is that on the architecture we are using (e.g., BlueGene L/P/Q) we want to align the memory usage properly in order to avoid cache misses. So we write a function smalloc() that takes care of all that for us, which new would not do if we are using the GNU compiler (maybe IBM's compiler will do it automatically, I don't know). We can also write into it code to verify the allocation was successful and if not, report the class and function where the error occured,

    Float *ptr=(Float *)smalloc(num_bytes,cname,fname);

    Also Float is some typedef, probably float or double, depending on the precision we've decided to use.

    Another use, which looks dangerous, but we do it anyways because we care more about performance than C++ doctrine, is something like:

    int num_bytes = N*2*sizeof(Float);
    Complex *c=(Complex *c)smalloc(num_bytes);
    // next, code that fills in the array of complex numbers ...
    Float *temp=(Float *)c;
    // Now we want the norm of the complex vector, and we use an optimized function that say uses standard reduction algorithm to take advantage of multithreading on our 64-way processor, or maybe on a GPU accelerator:
    Float my_norm=norm(temp,N*2);

    By converting to Float array, the complex array looks just like an array of floats 2*N long and norm knows what to do with such an array. If norm is a library function that we a calling from some external package, it probably has no idea what to do with our abstract Complex type. So we trick it. Is it "dangerous"? Maybe, but so is driving a car.

    Another trick may be if we have to call MPI_Send() and MPI_Recv() to send the array of Complex numbers between compute nodes. Then Float* typically means something to these functions, but Complex* does not. It would be ridiculous to copy the array elements into a float array just to communicate them when a simple type casting does the job (provided we do not use the C++ standard complex class, but rather our own class with just two data elements --- note that they also cannot be private, again for performance reasons --- we'd be friending practically every function in our code, which is plain silly).

  • Thanks for all these great examples and the helpful summary. Next time someone asks me why type safety matters I'm going to send them here.

  • This heirarchy can cause some interesting issues. For example, you might expect the expression 5u - 10 to evalute to -5 (5u means 5 as an unsigned integer). But in this case, the signed integer (10) is promoted to an unsigned integer, and the result of this expression is the unsigned integer 4294967291!

    "Interesting" is one way to describe it....

  • vinu

    The following snippet of code explained in this chapter isn't working as described.

    In the following program, the compiler will typically complain that converting a double to an int may result in loss of data:

    int nValue = 100;
    nValue = nValue / 2.5;

    my code:
    {
    int ivalue = 100;
    cout << "output" << ivalue/2.5 <<endl;
    ivalue = ivalue / 2.5;
    cout << "ivalue" <<ivalue << endl;
    return 0;
    }

    Both the cout returns 40, no compiler warning or error. I am using codeblocks.

    secondly, if the hierarchical nature of type conversion is true then why doesn't the following implicit cast of ivalue produce compiler error ?
    int main()
    {
    int ivalue = 0;
    double dvalue1 = 3.54567;
    double dvalue2 = 2.34345;
    ivalue = dvalue1 / dvalue2;
    cout << "ivalue" <<ivalue << endl;
    return 0;
    }

  • SamHocevar

    I would like to point out that "int nValue = 10 * 2.7" does not really work "as expected". It is not possible to store the value 2.7 in a double. Instead, the value stored in the machine register is something like 2.7000000000000001776... (continuing to 51 digits). You are lucky that the IEEE rounding rules allow the computation to give 27 at the end, but in general you should never expect a floating-point computation on inexact values to give an exact result.

    The value 2.5 can be represented exactly as a double, however. So you are guaranteed that 10 * 2.5 will be 25.

  • Konda

    Hello, I have a question: Is it safe to use static_cast([float value here]) to truncate a float value? And no, I'm not talking about rounding.

  • surya

    thank you zingmars

  • surya

    sry

    link is

    http://pastebin.mozilla.org/1253503

    • This is what compiler does too - it does whatever you ask from it, and assigns the results to a memory location, and then you store it in p1.
      And no - the line does not work. Firstly because apparently you can't do static cast on pointers (they're memory locations, not values or types or anything).
      Also - one does not simply convert one class to another. It doesn't work that way.
      Just my 2 cents.
      So I guess the answer to the 'what does it hold' is - nothing. It holds nothing because you can't do things this way.

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">