Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Mixed-Type Arithmetic Conversions
Predict what type results when mixing different numeric types in an expression.
Arithmetic Conversions
In earlier lessons, we learned about operator precedence and associativity, which determine how expressions evaluate.
Consider this straightforward expression:
int result { 7 + 8 };
The binary operator+ receives two operands, both of type int. Since operands match, the calculation uses this type, returning an int value. Thus 7 + 8 evaluates to int value 15.
What happens when operands have different types?
??? result { 7 + 8.5 };
Here operator+ gets one int and one double operand. Should it return an int, double, or something else?
In C++, certain operators require matching operand types. When invoked with mismatched types, one or both operands undergo implicit conversion following rules called usual arithmetic conversions. The resulting matching type is the common type.
Operators Requiring Matching Operands
These operators need operands of the same type:
- Binary arithmetic: +, -, *, /, %
- Binary relational: <, >, <=, >=, ==, !=
- Binary bitwise: &, ^, |
- Conditional operator ?: (excluding the condition, which should be bool)
Advanced note: Overloaded operators don't follow usual arithmetic conversion rules.
The Usual Arithmetic Conversion Rules
The compiler maintains a type ranking hierarchy resembling:
- long double (highest)
- double
- float
- long long
- long
- int (lowest)
Rules for finding the common type:
Step 1:
- If one operand is integral and the other floating-point, convert the integral operand to the floating-point type (no integral promotion occurs).
- Otherwise, promote any integral operands numerically.
Step 2:
- After promotion, if signs differ (one signed, one unsigned), special rules apply (see below)
- Otherwise, convert the lower-ranked operand to the higher-ranked type.
Advanced note: Special rules for mixed-sign integral operands: If the unsigned operand outranks or equals the signed operand, convert signed to unsigned. If the signed type can represent all unsigned type values, convert unsigned to signed. Otherwise, convert both to the unsigned version of the signed type. Complete rules available in the C++ reference documentation.
Examples
Let's examine some examples using typeid (from
Adding int and double:
#include <iostream>
#include <typeinfo>
int main()
{
int x{ 10 };
std::cout << typeid(x).name() << '\n';
double y{ 20.5 };
std::cout << typeid(y).name() << '\n';
std::cout << typeid(x + y).name() << ' ' << x + y << '\n';
return 0;
}
The double operand has higher priority, so the int operand (value 10) converts to double value 10.0. Then double values 10.0 and 20.5 add to produce double result 30.5.
Sample output:
int
double
double 30.5
Note: typeid.name() output varies by compiler implementation.
Adding two short values:
#include <iostream>
#include <typeinfo>
int main()
{
short m{ 12 };
short n{ 15 };
std::cout << typeid(m + n).name() << ' ' << m + n << '\n';
return 0;
}
Neither operand appears in the priority list, so both undergo integral promotion to int. Adding two ints produces an int:
int 27
Signed and Unsigned Problems
The prioritization and conversion rules create issues when mixing signed and unsigned values. Consider:
#include <iostream>
#include <typeinfo>
int main()
{
std::cout << typeid(10u - 25).name() << ' ' << 10u - 25 << '\n';
return 0;
}
You might expect 10u - 25 to evaluate to -15 since 10 - 25 = -15. Actual result:
unsigned int 4294967281
Conversion rules change the int operand to unsigned int. Since -15 exceeds unsigned int range, we get an unexpected result.
Another counterintuitive example:
#include <iostream>
int main()
{
std::cout << std::boolalpha << (-10 < 20u) << '\n';
return 0;
}
Obviously 20 exceeds -10, yet when evaluating, -10 converts to a large unsigned int exceeding 20. This prints false instead of the expected true.
This demonstrates a primary reason to avoid unsigned integers: mixing them with signed integers in arithmetic expressions risks unexpected results, often without compiler warnings.
Summary
Usual arithmetic conversions: Rules that determine how the compiler converts operands to matching types when binary operators receive mismatched operand types.
Common type: The resulting matching type after usual arithmetic conversions are applied.
Type ranking: Compiler maintains a hierarchy (long double > double > float > long long > long > int) to determine which type to convert to.
Conversion process: First, promote integral operands if one is floating-point, or apply numeric promotion if both are integral. Second, if signs differ after promotion, apply special mixed-sign rules; otherwise, convert lower-ranked to higher-ranked type.
Signed/unsigned mixing danger: When mixing signed and unsigned operands, the signed value converts to unsigned, which can produce unexpected results (especially with negative values) without compiler warnings.
std::common_type: Utility from <type_traits> that determines the common type of two types according to usual arithmetic conversion rules.
Understanding arithmetic conversions helps you predict expression types and avoid subtle bugs from unexpected type conversions, especially when mixing signed and unsigned integers.
std::common_type and std::common_type_t
Sometimes knowing the common type of two types proves useful. std::common_type and the type alias std::common_type_t (both in <type_traits>) serve this purpose.
For example, std::common_type_t<int, double> returns the common type of int and double, while std::common_type_t<unsigned int, long> returns the common type of unsigned int and long.
We'll demonstrate this utility in later lessons on function templates with multiple template types.
Mixed-Type Arithmetic Conversions - Quiz
Test your understanding of the lesson.
Practice Exercises
Arithmetic Conversions
Practice understanding the usual arithmetic conversion rules. Learn how C++ determines the resulting type when mixing different numeric types in expressions.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!