First off, I am aware that this is an extremely simple question. I'm just looking for a technical explanation as to why the compiler decides to make the following variable with an auto type specifier the type double over int:
int value1 = 5;
double value2 = 2.2;
auto value3 = value1 * value2;
I know that the compiler will derive the double type for value3 from the initialized value, but why exactly is that?
auto variable types are defined in terms of template type deduction. Like this:
template<typename T>
void f(T t);
f(value1 * value2); // will call f<double>()
The reason value1 * value2 gives double rather than int is because the arithmetic conversion rules allow turning int into double (the reverse is an implicit conversion also but not an arithmetic conversion). When you use operators on built-in types, "the usual arithmetic conversions are applied".
Here's the rule found in section 5 (Expressions) of the Standard:
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, 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 shall be performed on both operands.
Because when multiplying an int by a double you get a double.
C and C++ compilers always promote basic numeric types to the most general type included in an expression. So, any expression involving two int values yields an int, but if either of the operands is double, then the expression value will also be double.
Related
This question already has answers here:
Implicit type promotion rules
(4 answers)
Closed 1 year ago.
Some code called a templated function, but it did not call the specialisation I expected. Turned out the culprit was this: I or-ed some uint16_t constants and passed them as an argument like this: foo(uint16_bar | uint16_baz);. My expectation was it would call the foo<uint16_t>, but it did not. I can easily evade the problem with a cast, but I really wonder what the reason for this might be. Especially since not every integral type you pass it will return type int.
Case 1:
uint16_t a = 2;
uint16_t b = 4;
auto x = (a|b); // x is of type 'int': Surprize!
Case 2:
uint a = 2;
uint b = 4;
auto x = (a|b); // x is of type unsigned_int: What I would expect
Why does (a|b) in case 1 not return a value of type uint16_t ?
The usual arithmetic conversions are applied to operands of the bitwise operator |. That means that integer objects of types with rank less than the rank of the type int are converted either to int or unsigned int due to the integral promotions that are a part of the usual arithmetic conversions..
From the C++ 14 Standard ( 5.13 Bitwise inclusive OR operator)
1 The usual arithmetic conversions are performed; the result is the
bitwise inclusive OR function of its operands. The operator applies
only to integral or unscoped enumeration operands.
and (4.5 Integral promotions)
1 A prvalue of an integer type other than bool, char16_t, char32_t, or
wchar_t whose integer conversion rank (4.13) is less than the rank of
int can be converted to a prvalue of type int if int can represent all
the values of the source type; otherwise, the source prvalue can be
converted to a prvalue of type unsigned int
A quirk of built-in operators is that they will perform integral promotion on small arguments:
cppreference on implicit conversions:
prvalues of small integral types (such as char) may be converted to prvalues of larger integral types (such as int). In particular, arithmetic operators do not accept types smaller than int as arguments, and integral promotions are automatically applied after lvalue-to-rvalue conversion, if applicable. This conversion always preserves the value.
In your code's case, the bitwise OR operator is promoting your uint16_ts to int before applying the operator.
I was writing some code recently that was actually supposed to test other code, and I stumbled upon a surprising case of integer promotion. Here's the minimal testcase:
#include <cstdint>
#include <limits>
int main()
{
std::uint8_t a, b;
a = std::numeric_limits<std::uint8_t>::max();
b = a;
a = a + 1;
if (a != b + 1)
return 1;
else
return 0;
}
Surprisingly this program returns 1. Some debugging and a hunch revealed that b + 1 in the conditional was actually returning 256, while a + 1 in assignment produced the expected value of 0.
Section 8.10.6 (on the equality/ineuqlity operators) of the C++17 draft states that
If both operands are of arithmetic or enumeration type, the usual arithmetic conversions are performed on
both operands; each of the operators shall yield true if the specified relationship is true and false if it is
false.
What are "the usual arithmetic conversions", and where are they defined in the standard? My guess is that they implicitly promote smaller integers to int or unsigned int for certain operators (which is also supported by the fact that replacing std::uint8_t with unsigned int yields 0, and further in that the assignment operator lacks the "usual arithmetic conversions" clause).
What are "the usual arithmetic conversions", and where are they defined in the standard?
[expr.arith.conv]/1
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:
(1.1) If either operand is of scoped enumeration type, no conversions
are performed; if the other operand does not have the same type, the
expression is ill-formed.
(1.2) If either operand is of type long double, the other shall be
converted to long double.
(1.3) Otherwise, if either operand is double, the other shall be
converted to double.
(1.4) Otherwise, if either operand is float, the other shall be
converted to float.
(1.5) Otherwise, the integral promotions ([conv.prom]) shall be
performed on both operands.59 Then the following rules shall be
applied to the promoted operands:
(1.5.1) If both operands have the same type, no further conversion is
needed.
(1.5.2) 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.
(1.5.3) 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.
(1.5.4) 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.
(1.5.5) Otherwise, both operands shall be converted to the unsigned
integer type corresponding to the type of the operand with signed
integer type.
59) As a consequence, operands of type bool, char8_t, char16_t,
char32_t, wchar_t, or an enumerated type are converted to some
integral type.
For uint8_t vs int (for operator+ and operator!= later), #1.5 is applied, uint8_t will be promoted to int, and the result of operator+ is int too.
On the other hand, for unsigned int vs int (for operator+), #1.5.3 is applied, int will be converted to unsigned int, and the result of operator+ is unsigned int.
Your guess is correct. Operands to many operators in C++ (e.g., binary arithmetic and comparison operators) are subject to the usual arithmetic conversions. In C++17, the usual arithmetic conversions are specified in [expr]/11. I'm not going to quote the whole paragraph here because it's rather large (you can just click on the link), but for integral types, the usual arithmetic conversions boil down to integral promotions being applied followed by effectively some more promoting in the sense that if the types of the two operands after the initial integral promotions are not the same, the smaller type is converted to the larger one of the two. The integral promotions basically mean that any type smaller than an int will be promoted to int or unsigned int, whichever of the two can represent all possible values of the original type, which is mainly what is causing the behavior in your example.
As you have already figured out yourself, in your code, the usual arithmetic conversions happen in a = a + 1; and, most noticeably, in the condition of your if
if (a != b + 1)
…
where they cause b to be promoted to int, making the result of b + 1 to be of type int, as well as a being promoted to int and the !=, thus, happening on values of type int, which causes the condition to be true rather than false…
I have the following code:
uint16_t getLastMarker(const std::string &number);
...
const auto msgMarker = getLastMarker(msg.number) + static_cast<uint16_t>(1);
static_assert(std::is_same<decltype(msgMarker), const int>::value, "Should fail");
static_assert(std::is_same<decltype(msgMarker), const uint16_t>::value, "Should not fail");
and I expect that the first assertion will fail and second one will not. However gcc 4.9.2 and clang 3.6 do the opposite. If I use uint16_t instead of auto in my code proper assertion fails and another one succeeds.
P.S. Initially I had just 1 instead of static_cast<uint16_t>(1) and thought that the issue is caused by the fact that numeric literal 1 has type int but wrong assertion fails even after explicit cast here.
Addition will perform the usual arithmetic conversions on its operands which in this case will result in the operands being promoted to int due the the integer promotions and the result will also be int.
You can use uint16_t instead of auto to force a conversion back or in the general case you can use static_cast.
For a rationale as to why type smaller than int are promoted to larger types see Why must a short be converted to an int before arithmetic operations in C and C++?.
For reference, from the draft C++ standard section 5.7 Additive operators:
[...]The usual arithmetic conversions are performed for operands of
arithmetic or enumeration type[...]
and from section 5 Expressions:
[...]Otherwise, the integral promotions (4.5) shall be performed on
both operands.59 Then the following rules shall be applied
to the promoted operands[...]
and from section 4.5 Integral promotions (emphasis mine):
A prvalue of an integer type other than bool, char16_t, char32_t, or
wchar_t whose integer conversion rank (4.13) is less than the rank
of int can be converted to a prvalue of type int if int can represent
all the values of the source type; otherwise, the source prvalue can
be converted to a prvalue of type unsigned int.
Assuming int is larger than 16-bit.
Arithmetic operations don't work on any type smaller than int. So, if uint16_t is smaller than int, it will be promoted to int (or possibly a larger type, if necessary to match the other operand) before performing the addition.
The result of the addition will be the promoted type. If you want another type, you'll have to convert afterwards.
So, if I understood it well, integral promotion provides that: char, wchar_t, bool, enum, short types ALWAYS are converted to int (or unsigned int). Then, if there are different types in an expression, further conversion will be applied.
Am I understanding this well?
And if yes, then my question: Why is it good? Why? Don't become char/wchar_t/bool/enum/short unnecessary? I mean for example:
char c1;
char c2;
c1 = c2;
As I described before, char ALWAYS is converted to int, so in this case after automatic converting this looks like this:
int c1;
int c2;
c1 = c2;
But I can't understand why is this good, if I know that char type will be enough for my needs.
Storage types are never automatically converted. You only get automatic integer promotion as soon as you start doing integer arithmetics (+, -, bitshifts, ...) on those variables.
char c1, c2; // stores them as char
char c3 = c1 + c2; // equivalent to
char c3 = (char)((int)c1 + (int)c2);
The conversions you're asking about are the usual arithmetic conversions and the integer promotions, defined in section 6.3.1.8 of the latest ISO C standard. They're applied to the operands of most binary operators ("binary" meaning that they take two operands, such as +, *, etc.). (The rules are similar for C++. In this answer, I'll just refer to the C standard.)
Briefly the usual arithmetic conversions are:
If either operand is long double, the other operand is converted to long double.
Otherwise, if either operand is double, the other operand is converted to double.
Otherwise, if either operand is float, the other operand is converted to float.
Otherwise, the integer promotions are performed on both operands, and then some other rules are applied to bring the two operands to a common type.
The integer promotions are defined in section 6.3.1.1 of the C standard. For a type narrower than int, if the type int can hold all the values of the type, then an expression of that type is converted to int; otherwise it's converted to unsigned int. (Note that this means that an expression of type unsigned short may be converted either to int or to unsigned int, depending on the relative ranges of the types.)
The integer promotions are also applied to function arguments when the declaration doesn't specify the type of the parameter. For example:
short s = 2;
printf("%d\n", s);
promotes the short value to int. This promotion does not occur for non-variadic functions.
The quick answer for why this is done is that the standard says so.
The underlying reason for all this complexity is to allow for the restricted set of arithmetic operations available on most CPUs. With this set of rules, all arithmetic operators (other than the shift operators, which are a special case) are only required to work on operands of the same type. There is no short + long addition operator; instead, the short operand is implicitly converted to long. And there are no arithmetic operators for types narrower than int; if you add two short values, both arguments are promoted to int, yielding an int result (which might then be converted back to short).
Some CPUs can perform arithmetic on narrow operands, but not all can do so. Without this uniform set of rules, either compilers would have to emulate narrow arithmetic on CPUs that don't support it directly, or the behavior of arithmetic expressions would vary depending on what operations the target CPU supports. The current rules are a good compromise between consistency across platforms and making good use of CPU operations.
if I understood it well, integral promotion provides that: char, wchar_t, bool, enum, short types ALWAYS converted to int (or unsigned int).
Your understanding is only partially correct: short types are indeed promoted to int, but only when you use them in expressions. The conversion is done immediately before the use. It is also "undone" when the result is stored back.
The way the values are stored remains consistent with the properties of the type, letting you control the way you use your memory for the variables that you store. For example,
struct Test {
char c1;
char c2;
};
will be four times as small as
struct Test {
int c1;
int c2;
};
on systems with 32-bit ints.
The conversion is not performed when you store the value in the variable. The conversion is done if you cast the value or if you perform some operation like some arithmetic operation on it explicitly
It really depends on your underlying microprocessor architecture. For example, if your processor is 32-bit, that is its native integer size. Using its native integer size in integer computations is better optimized.
Type conversion takes place when arithmetic operations, shift operations, unary operations are performed. See what standard says about it:
C11; 6.3.1.4 Real floating and integer:
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.58) All other types are unchanged by the
integer promotions.
58.The integer promotions are applied only: as part of the usual arithmetic conversions, to certain argument expressions, to the operands of the unary +, -, and ~ operators, and to both operands of the shift operators,1 as specified by their respective subclauses
1. Emphasis is mine.
I want to make sure that my understanding of the return type of C++ division,
int / int => return is int?
float / float => return is which type? float?
double /double => return is double?
int / double => return is double?
int / float => return is float?
Please correct me if I am wrong.
All of those are correct. Here's what the C++03 standard says (§5/9):
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 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.
Then, if either operand is unsigned long the other shall be converted to unsigned long.
Otherwise, if one operand is a long int and the other unsigned int, then if a long int can represent all the values of an unsigned int, the unsigned int shall be converted to a long int; otherwise both operands shall be converted to unsigned long int.
Otherwise, if either operand is long, the other shall be converted to long.
Otherwise, if either operand is unsigned, the other shall be converted to unsigned.
[Note: otherwise, the only remaining case is that both operands are int]
operator/ for basic data types (just like most, if not all, operators for basic types) returns the strongest type of its two operands.
The answer to all of your questions is thus yes.
In general, floating point types are stronger than integer ones and unsigned are stronger than signed...
Defining > as "stronger than", we can say that:
long double > double > float > unsigned long > long > unsigned int > int > unsigned short > short > unsigned char > char
You are correct in all cases. The rules for operations involving at least one floating point type are that if either type is a long double, the result is long double; otherwise, if either type is double the result is double otherwise the result has type float.
Arithmetic operations between two ints produce an int result.
The rules between other types are slightly more complex and can be implementation dependent; for almost all operations integer promotions mean that the operands are promoted to at least an int sized types producing at least an int sized result.
Considering only three types (float, double and int):
If any of the operand is double, then the result will be double.
Else if any of the operand is float, then the result will be float.
Else the result will be int .
The result will be typed (if rule exists) for the assignment. If you have int x = dY + iZ; Then promotion will cause the addition result to be double, but it will be converted to an int when its assigned to x. Google "variable promotion in c++" for more details.
Roughly speaking, in C++, in any scenario, both operands are converted to the "largest" type of the two operands' types before the operation is executed. See MSDN Standard Arithmetic Conversions.