Are the "Usual Arithmetic Conversions" and the "Integer Promotions" the Same Thing

Are the usual arithmetic conversions and the integer promotions the same thing?

No.

The usual arithmetic conversions involve integral promotion under certain circumstances, but these are two separate mechanisms:

[C++14: 5/10]: Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:

  • If either operand is of scoped enumeration type (7.2), no conversions are performed; if the other operand does not have the same type, the expression is ill-formed.
  • If either operand is of type long double, the other shall be converted to long double.
  • Otherwise, if either operand is double, the other shall be converted to double.
  • Otherwise, if either operand is float, the other shall be converted to float.
  • Otherwise, the integral promotions (4.5) shall be performed on both operands.61 Then the following rules shall be applied to the promoted operands:
    • If both operands have the same type, no further conversion is needed.
    • Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank shall be converted to the type of the operand with greater rank.
    • Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.
    • Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type.
    • Otherwise, both operands shall be converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

The wording is much the same in C11.

Note that the integral promotions may also be performed under circumstances that have nothing to do with the usual arithmetic conversions, e.g. the LHS operand of a bit-shift; ultimately, all this is why the two mechanisms have their own distinct names!

What's the difference between integer promotions and integer conversions in C++

I think that the distinction is important because both do not fall in the same conversion category and have different rank (see 13.3.3.1.1, Standard conversion sequences). The rank makes a difference when it comes to overload resolution :

Standard conversion sequences are ordered by their ranks: an Exact Match is a better conversion than a Promotion,
which is a better conversion than a Conversion.

In the end, I believe it is the distinction between 4.5 and 4.7 that makes the following code unambiguous :

#include <iostream>

void foo(int i) { std::cout << "foo(int)" << std::endl; }
void foo(unsigned short i) { std::cout << "foo(unsigned short)" << std::endl; }

int main()
{
foo(static_cast<short>(1));
}
  • short to int is a promotion (thus having promotion rank)
  • short to unsigned short is a conversion (thus having conversion rank)

In the end, this code calls foo(int) because it is a better candidate.

Are the usual arithmetic conversions and the integer promotions the same thing?

No.

The usual arithmetic conversions involve integral promotion under certain circumstances, but these are two separate mechanisms:

[C++14: 5/10]: Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions, which are defined as follows:

  • If either operand is of scoped enumeration type (7.2), no conversions are performed; if the other operand does not have the same type, the expression is ill-formed.
  • If either operand is of type long double, the other shall be converted to long double.
  • Otherwise, if either operand is double, the other shall be converted to double.
  • Otherwise, if either operand is float, the other shall be converted to float.
  • Otherwise, the integral promotions (4.5) shall be performed on both operands.61 Then the following rules shall be applied to the promoted operands:
    • If both operands have the same type, no further conversion is needed.
    • Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank shall be converted to the type of the operand with greater rank.
    • Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type.
    • Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type.
    • Otherwise, both operands shall be converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

The wording is much the same in C11.

Note that the integral promotions may also be performed under circumstances that have nothing to do with the usual arithmetic conversions, e.g. the LHS operand of a bit-shift; ultimately, all this is why the two mechanisms have their own distinct names!

Arithmetic conversion VS integral promotion

The additive operators perform what is called the usual arithmetic conversion on their operands which can include integral promotions and then after that we can have further conversions. The purpose is to yield a common type and if the promotions do not accomplish that then a further conversion is required.

This is covered in section 5 [expr] of the draft C++ standard which says (emphasis mine):

Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield
result types in a similar way. The purpose is to yield a common type, which is also the type of the result.
This pattern is called the usual arithmetic conversions, which are defined as follow

and includes the following bullet:

  • Otherwise, the integral promotions (4.5) shall be performed on both operands.61 Then the following
    rules shall be applied to the promoted operands:

which has the following bullets:

  • If both operands have the same type, no further conversion is needed

  • Otherwise, if both operands have signed integer types or both have unsigned integer types, the
    operand with the type of lesser integer conversion rank shall be converted to the type of the
    operand with greater rank
    .

  • Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the
    rank of the type of the other operand, the operand with signed integer type shall be converted to
    the type of the operand with unsigned integer type.
  • Otherwise, if the type of the operand with signed integer type can represent all of the values of
    the type of the operand with unsigned integer type, the operand with unsigned integer type shall
    be converted to the type of the operand with signed integer type.
  • Otherwise, both operands shall be converted to the unsigned integer type corresponding to the
    type of the operand with signed integer type.

So in the first case after promotions they both have the same type(int) so no further conversion is needed.

In the second case after promotions they do not(int and long) so a further conversion is required.

Usual arithmetic conversions in C : Whats the rationale behind this particular rule

No that's not actually true. C99 Section 5.1.2.3 Program execution, clause 10 covers exactly the case you ask about:

EXAMPLE 2

In executing the fragment

char c1, c2;
c1 = c1 + c2;
the "integer promotions" require that the abstract machine promote the value of each variable to int size and then add the two ints and truncate the sum.

Provided the addition of two chars can be done without overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only produce the same result, possibly omitting the promotions.

So, if the operation is known to produce the same result, there's no requirement for using the wider values.

But if you want the rationale behind a specific decision made in the standard, you have to look at, ..... wait for it, ..... yes, the Rationale document :-)

In section 6.3.1.8 of that rationale (sections match those in the standard), it states:

Explicit license was added to perform calculations in a “wider” type than absolutely necessary, since this can sometimes produce smaller and faster code, not to mention the correct answer more often.

Calculations can also be performed in a “narrower” type by the as if rule so long as the same end result is obtained.

Understanding the order of conversions, arithmetic conversions, and integer promotions for non-overloaded bitwise operators

When the compiler sees this:

int z = x & y;

It will see that there is no specific operator & for std::integral_constant<>. It will see however that there is a non-explicit operator value_type() for x and y. Since value_type is int, this gives a direct match for the most common operator &.

No arithmetic conversion or integral promotion is required or performed.

[conv] (2.1) says:

When used as operands of operators. The operator’s requirements for its operands dictate the destination type.

[over.match] says:

Each of these contexts defines the set of candidate functions and the list of arguments in its own unique way.
But, once the candidate functions and argument lists have been identified, the selection of the best function
is the same in all cases:

  • (2.8) — First, a subset of the candidate functions (those that have the proper number of arguments and meet
    certain other conditions) is selected to form a set of viable functions (13.3.2).
  • (2.9) — Then the best viable function is selected based on the implicit conversion sequences (13.3.3.1) needed
    to match each argument to the corresponding parameter of each viable function.

[class.conv] says:

Type conversions of class objects can be specified by constructors and by conversion functions. These
conversions are called user-defined conversions and are used for implicit type conversions (Clause 4), for
initialization (8.5), and for explicit type conversions (5.4, 5.2.9).

Implicit type promotion rules

C was designed to implicitly and silently change the integer types of the operands used in expressions. There exist several cases where the language forces the compiler to either change the operands to a larger type, or to change their signedness.

The rationale behind this is to prevent accidental overflows during arithmetic, but also to allow operands with different signedness to co-exist in the same expression.

Unfortunately, the rules for implicit type promotion cause much more harm than good, to the point where they might be one of the biggest flaws in the C language. These rules are often not even known by the average C programmer and therefore cause all manner of very subtle bugs.

Typically you see scenarios where the programmer says "just cast to type x and it works" - but they don't know why. Or such bugs manifest themselves as rare, intermittent phenomena striking from within seemingly simple and straight-forward code. Implicit promotion is particularly troublesome in code doing bit manipulations, since most bit-wise operators in C come with poorly-defined behavior when given a signed operand.



Integer types and conversion rank

The integer types in C are char, short, int, long, long long and enum.

_Bool/bool is also treated as an integer type when it comes to type promotions.

All integers have a specified conversion rank. C11 6.3.1.1, emphasis mine on the most important parts:

Every integer type has an integer conversion rank defined as follows:

— No two signed integer types shall have the same rank, even if they have the same representation.

— The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.

— The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.

— The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type, if any.


— The rank of any standard integer type shall be greater than the rank of any extended integer type with the same width.

— The rank of char shall equal the rank of signed char and unsigned char.

— The rank of _Bool shall be less than the rank of all other standard integer types.

— The rank of any enumerated type shall equal the rank of the compatible integer type (see 6.7.2.2).

The types from stdint.h sort in here too, with the same rank as whatever type they happen to correspond to on the given system. For example, int32_t has the same rank as int on a 32 bit system.

Further, C11 6.3.1.1 specifies which types are regarded as the small integer types (not a formal term):

The following may be used in an expression wherever an int or unsigned int may
be used:

— An object or expression with an integer type (other than int or unsigned int) whose integer conversion rank is less than or equal to the rank of int and unsigned int.

What this somewhat cryptic text means in practice, is that _Bool, char and short (and also int8_t, uint8_t etc) are the "small integer types". These are treated in special ways and subject to implicit promotion, as explained below.



The integer promotions

Whenever a small integer type is used in an expression, it is implicitly converted to int which is always signed. This is known as the integer promotions or the integer promotion rule.

Formally, the rule says (C11 6.3.1.1):

If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int. These are called the integer promotions.

This means that all small integer types, no matter signedness, get implicitly converted to (signed) int when used in most expressions.

This text is often misunderstood as: "all small signed integer types are converted to signed int and all small, unsigned integer types are converted to unsigned int". This is incorrect. The unsigned part here only means that if we have for example an unsigned short operand, and int happens to have the same size as short on the given system, then the unsigned short operand is converted to unsigned int. As in, nothing of note really happens. But in case short is a smaller type than int, it is always converted to (signed) int, regardless of it the short was signed or unsigned!

The harsh reality caused by the integer promotions means that almost no operation in C can be carried out on small types like char or short. Operations are always carried out on int or larger types.

This might sound like nonsense, but luckily the compiler is allowed to optimize the code. For example, an expression containing two unsigned char operands would get the operands promoted to int and the operation carried out as int. But the compiler is allowed to optimize the expression to actually get carried out as an 8-bit operation, as would be expected. However, here comes the problem: the compiler is not allowed to optimize out the implicit change of signedness caused by the integer promotion because there is no way for the compiler to tell if the programmer is purposely relying on implicit promotion to happen, or if it is unintentional.

This is why example 1 in the question fails. Both unsigned char operands are promoted to type int, the operation is carried out on type int, and the result of x - y is of type int. Meaning that we get -1 instead of 255 which might have been expected. The compiler may generate machine code that executes the code with 8 bit instructions instead of int, but it may not optimize out the change of signedness. Meaning that we end up with a negative result, that in turn results in a weird number when printf("%u is invoked. Example 1 could be fixed by casting the result of the operation back to type unsigned char.

With the exception of a few special cases like ++ and sizeof operators, the integer promotions apply to almost all operations in C, no matter if unary, binary (or ternary) operators are used.



The usual arithmetic conversions

Whenever a binary operation (an operation with 2 operands) is done in C, both operands of the operator have to be of the same type. Therefore, in case the operands are of different types, C enforces an implicit conversion of one operand to the type of the other operand. The rules for how this is done are named the usual artihmetic conversions (sometimes informally referred to as "balancing"). These are specified in C11 6.3.18:

(Think of this rule as a long, nested if-else if statement and it might be easier to read :) )

6.3.1.8 Usual arithmetic conversions

Many operators that expect operands of arithmetic type cause conversions and yield result
types in a similar way. The purpose is to determine a common real type for the operands
and result. For the specified operands, each operand is converted, without change of type
domain, to a type whose corresponding real type is the common real type. Unless
explicitly stated otherwise, the common real type is also the corresponding real type of
the result, whose type domain is the type domain of the operands if they are the same,
and complex otherwise. This pattern is called the usual arithmetic conversions:

  • First, if the corresponding real type of either operand is long double, the other operand is converted, without change of type domain, to a type whose corresponding real type is long double.
  • Otherwise, if the corresponding real type of either operand is double, the other operand is converted, without change of type domain, to a type whose corresponding real type is double.
  • Otherwise, if the corresponding real type of either operand is float, the other operand is converted, without change of type domain, to a type whose corresponding real type is float.
  • Otherwise, the integer promotions are performed on both operands. Then the
    following rules are applied to the promoted operands:
  • If both operands have the same type, then no further conversion is needed.
  • Otherwise, if both operands have signed integer types or both have unsigned
    integer types, the operand with the type of lesser integer conversion rank is
    converted to the type of the operand with greater rank.
  • Otherwise, if the operand that has unsigned integer type has rank greater or
    equal to the rank of the type of the other operand, then the operand with
    signed integer type is converted to the type of the operand with unsigned
    integer type.
  • Otherwise, if the type of the operand with signed integer type can represent
    all of the values of the type of the operand with unsigned integer type, then
    the operand with unsigned integer type is converted to the type of the
    operand with signed integer type.
  • Otherwise, both operands are converted to the unsigned integer type
    corresponding to the type of the operand with signed integer type.

Notable here is that the usual arithmetic conversions apply to both floating point and integer variables. In the case of integers, we can also note that the integer promotions are invoked from within the usual arithmetic conversions. And after that, when both operands have at least the rank of int, the operators are balanced to the same type, with the same signedness.

This is the reason why a + b in example 2 gives a strange result. Both operands are integers and they are at least of rank int, so the integer promotions do not apply. The operands are not of the same type - a is unsigned int and b is signed int. Therefore the operator b is temporarily converted to type unsigned int. During this conversion, it loses the sign information and ends up as a large value.

The reason why changing type to short in example 3 fixes the problem, is because short is a small integer type. Meaning that both operands are integer promoted to type int which is signed. After integer promotion, both operands have the same type (int), no further conversion is needed. And then the operation can be carried out on a signed type as expected.

How does type conversion and integer promotion work for stdint.h?

To answer your first question: no. Since int32_t is usually defined with a typedef like this

typedef int int32_t;

it is the same as int and will have the same rank as int.

To answer the second question: yes. Integer promotion still applies. The types defined in stdint.h behave just like the types they are aliases of.

By the way, to be more confident in how your compiler behaves, you can test all of these things in your compiler by writing invalid code like this and carefully looking at the error message, which will (if you have a good compiler) reveal the type of the expression on the right hand side:

void * x = (signed char)-1 + (uint32_t)0;

Difference between ++a and a+1

The expression ++a increments a in the context of its type (8-bit unsigned) so wraps around to zero.

The expression a + 1 adds the 8-bit unsigned a to the literal 1 (which is an int) and, when you do that with disparate types, the smaller of them is usually "upgraded" to the other before adding. Hence you're then adding the two int values, 255 and 1, with no wrapping.

This is known in C as the "usual arithmetic conversions" and the section of the standard that deals with it, C11 6.3.1.8, begins:

Many operators that expect operands of arithmetic type cause conversions and yield result types in a similar way. The purpose is to determine a common real type for the operands and result.

After going through some floating point types, the following dictates how integral types are handled:

Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:

  • If both operands have the same type, then no further conversion is needed.
  • Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
  • Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
  • Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
  • Otherwise, both operands are converted to the unsigned integer type
    corresponding to the type of the operand with signed integer type.

You may think that it's the fourth bullet point that comes into play here but, in actual fact, it's the preamble combined with the first bullet point. The "integer promotions", detailed in C11 6.3.1.1, will actually take integral types of lesser or equal rank to int and turn them into an int (assuming that can represent all values of the original type, otherwise it makes it unsigned int).


The other difference, of course, is that the first one modifies a but the second one does not.



Related Topics



Leave a reply



Submit