Search

8.1 — Implicit type conversion (coercion)

Introduction to type conversion

The value of an object is stored as a sequence of bits, and the data type of the object 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 might be stored as binary 0000 0000 0000 0000 0000 0000 0000 0011, whereas floating point value 3.0 might be stored as binary 0100 0000 0100 0000 0000 0000 0000 0000.

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 the memory allocated for float variable f. Instead, it needs to convert the integer value 3 to the equivalent floating point number, which can then be stored in the memory allocated for f.

The process of converting a value from one data type to another data type is called a type conversion.

Type conversion can be invoked in one of two ways: either implicitly (as needed by the compiler), or explicitly (when requested by the programmer). We’ll cover implicit type conversion in this lesson, and explicit type conversions (casting) in upcoming lesson 8.5 -- Explicit type conversion (casting).

Implicit type conversion

Implicit type conversion (also called automatic type conversion or coercion) is performed automatically by the compiler when one data type is required, but a different data type is supplied. The vast majority of type conversions in C++ are implicit type conversions. For example, implicit type conversion happens in all of the following cases:

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

When the type of a return value is different from the function’s declared return type:

When using certain binary operators with operands of different types:

When using a non-Boolean value in an if-statement:

When an argument passed to a function is a different type than the function parameter:

What happens when a type conversion is invoked

When a type conversion is invoked (whether implicitly or explicitly), the compiler will determine whether it can convert the value from the current type to the desired type. If a valid conversion can be found, then the compiler will produce a new value of the desired type. Note that type conversions don’t change the value or type of the value or object being converted.

If the compiler can’t find an acceptable conversion, then the compile will fail with a compile error. Type conversions can fail for any number of reasons. For example, the compiler might not know how to convert a value between the original type and the desired type. In other cases, statements may disallow certain types of conversions. For example:

Even though the compiler knows how to convert a double value to an int value, such conversions are disallowed when using brace-initialization.

There are also cases where the compiler may not be able to figure out which of several possible type conversions is unambiguously the best one to use. We’ll see examples of this in lesson 8.11 -- Overloaded function resolution.

So how does the compiler actually determine whether it can convert a value from one type to another?

The standard conversions

The C++ language standard defines how different fundamental types (and in some cases, compound types) can be converted to other types. These conversion rules are called the standard conversions.

The standard conversions can be broadly divided into 4 categories, each covering different types of conversions:

When a type conversion is needed, the compiler will see if there are standard conversions that it can use to convert the value to the desired type. The compiler may apply zero, one, or more than one standard conversions in the conversion process.

As an aside...

How do you have a type conversion with zero conversions? As an example, on architectures where int and long both have the same size and range, the same sequence of bits is used to represent values of both types. Therefore, no actual conversion is needed to convert a value between those types -- the value can simply be copied.

The full set of rules describing how type conversions work is both lengthy and complicated, and for the most part, type conversion “just works”. In the next set of lessons, we’ll cover the most important things you need to know about type conversions. If finer detail is required for some uncommon case, the full rules are detailed in technical reference documentation for implicit conversions.

Let’s get to it!


8.2 -- Floating-point and integral promotion
Index
7.x -- Chapter 7 summary and quiz

51 comments to 8.1 — 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="">