Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Understanding Operator Precedence Rules
Predict expression results by understanding which operators evaluate first.
Operator precedence and associativity
Chapter introduction
This chapter builds on concepts from the Introduction to literals and operators lesson. A quick review follows:
An operation is a mathematical process involving zero or more input values (called operands) that produces a new value (called an output value). The specific operation to be performed is denoted by a construct (typically a symbol or pair of symbols) called an operator.
For example, as children we all learn that 10 + 5 equals 15. In this case, the literals 10 and 5 are the operands, and the symbol + is the operator that tells us to apply mathematical addition on the operands to produce the new value 15. Because there is only one operator being used here, this is straightforward.
In this chapter, we'll discuss topics related to operators and explore many of the common operators that C++ supports.
Evaluation of compound expressions
Now, let's consider a compound expression, such as 6 + 3 * 4. Should this be grouped as (6 + 3) * 4 which evaluates to 36, or 6 + (3 * 4) which evaluates to 18? Using normal mathematical precedence rules (which state that multiplication is resolved before addition), we know that the above expression should be grouped as 6 + (3 * 4) to produce the value 18. But how does the compiler know?
In order to evaluate an expression, the compiler must do two things:
- At compile time, the compiler must parse the expression and determine how operands are grouped with operators. This is done via the precedence and associativity rules, which we'll discuss momentarily.
- At compile time or runtime, the operands are evaluated and operations executed to produce a result.
Operator precedence
To assist with parsing a compound expression, all operators are assigned a level of precedence. Operators with a higher precedence level are grouped with operands first.
You can see in the table below that multiplication and division (precedence level 5) have a higher precedence level than addition and subtraction (precedence level 6). Thus, multiplication and division will be grouped with operands before addition and subtraction. In other words, 6 + 3 * 4 will be grouped as 6 + (3 * 4).
Operator associativity
Consider a compound expression like 12 - 5 - 2. Should this be grouped as (12 - 5) - 2 which evaluates to 5, or 12 - (5 - 2), which evaluates to 9? Since both subtraction operators have the same precedence level, the compiler cannot use precedence alone to determine how this should be grouped.
If two operators with the same precedence level are adjacent to each other in an expression, the operator's associativity tells the compiler whether to evaluate the operators (not the operands!) from left to right or from right to left. Subtraction has precedence level 6, and the operators in precedence level 6 have an associativity of left to right. So this expression is grouped from left to right: (12 - 5) - 2.
Table of operator precedence and associativity
The below table is primarily meant to be a reference chart that you can refer back to in the future to resolve any precedence or associativity questions you have.
Notes:
- Precedence level 1 is the highest precedence level, and level 17 is the lowest. Operators with a higher precedence level have their operands grouped first.
- L->R means left to right associativity.
- R->L means right to left associativity.
| Prec/Ass | Operator | Description | Pattern |
|---|---|---|---|
| 1 L->R | :: | Global scope (unary) | ::name |
| :: | Namespace scope (binary) | class_name::member_name | |
| 2 L->R | () | Parentheses | (expression) |
| () | Function call | function_name(arguments) | |
| type() | Functional cast | type(expression) | |
| type{} | List init temporary object (C++11) | type{expression} | |
| [] | Array subscript | pointer[expression] | |
| . | Member access from object | object.member_name | |
| -> | Member access from object ptr | object_pointer->member_name | |
| ++ | Post-increment | lvalue++ | |
| -- | Post-decrement | lvalue-- | |
| typeid | Run-time type information | typeid(type) or typeid(expression) | |
| const_cast | Cast away const | const_cast |
|
| dynamic_cast | Run-time type-checked cast | dynamic_cast |
|
| reinterpret_cast | Cast one type to another | reinterpret_cast |
|
| static_cast | Compile-time type-checked cast | static_cast |
|
| sizeof... | Get parameter pack size | sizeof...(expression) | |
| noexcept | Compile-time exception check | noexcept(expression) | |
| alignof | Get type alignment | alignof(type) | |
| 3 R->L | + | Unary plus | +expression |
| - | Unary minus | -expression | |
| ++ | Pre-increment | ++lvalue | |
| -- | Pre-decrement | --lvalue | |
| ! | Logical NOT | !expression | |
| not | Logical NOT | not expression | |
| ~ | Bitwise NOT | ~expression | |
| (type) | C-style cast | (new_type)expression | |
| sizeof | Size in bytes | sizeof(type) or sizeof(expression) | |
| co_await | Await asynchronous call | co_await expression (C++20) | |
| & | Address of | &lvalue | |
| * | Dereference | *expression | |
| new | Dynamic memory allocation | new type | |
| new[] | Dynamic array allocation | new type[expression] | |
| delete | Dynamic memory deletion | delete pointer | |
| delete[] | Dynamic array deletion | delete[] pointer | |
| 4 L->R | ->* | Member pointer selector | object_pointer->*pointer_to_member |
| .* | Member object selector | object.*pointer_to_member | |
| 5 L->R | * | Multiplication | expression * expression |
| / | Division | expression / expression | |
| % | Remainder | expression % expression | |
| 6 L->R | + | Addition | expression + expression |
| - | Subtraction | expression - expression | |
| 7 L->R | << | Bitwise shift left / Insertion | expression << expression |
| >> | Bitwise shift right / Extraction | expression >> expression | |
| 8 L->R | <=> | Three-way comparison (C++20) | expression <=> expression |
| 9 L->R | < | Comparison less than | expression < expression |
| <= | Comparison less than or equals | expression <= expression | |
| > | Comparison greater than | expression > expression | |
| >= | Comparison greater than or equals | expression >= expression | |
| 10 L->R | == | Equality | expression == expression |
| != | Inequality | expression != expression | |
| 11 L->R | & | Bitwise AND | expression & expression |
| 12 L->R | ^ | Bitwise XOR | expression ^ expression |
| 13 L->R | | | Bitwise OR | expression | expression |
| 14 L->R | && | Logical AND | expression && expression |
| and | Logical AND | expression and expression | |
| 15 L->R | || | Logical OR | expression || expression |
| or | Logical OR | expression or expression | |
| 16 R->L | throw | Throw expression | throw expression |
| co_yield | Yield expression (C++20) | co_yield expression | |
| ?: | Conditional | expression ? expression : expression | |
| = | Assignment | lvalue = expression | |
| *= | Multiplication assignment | lvalue *= expression | |
| /= | Division assignment | lvalue /= expression | |
| %= | Remainder assignment | lvalue %= expression | |
| += | Addition assignment | lvalue += expression | |
| -= | Subtraction assignment | lvalue -= expression | |
| <<= | Bitwise shift left assignment | lvalue <<= expression | |
| >>= | Bitwise shift right assignment | lvalue >>= expression | |
| &= | Bitwise AND assignment | lvalue &= expression | |
| |= | Bitwise OR assignment | lvalue |= expression | |
| ^= | Bitwise XOR assignment | lvalue ^= expression | |
| 17 L->R | , | Comma operator | expression, expression |
You should already recognize a few of these operators, such as +, -, *, /, (), and sizeof. However, unless you have experience with another programming language, the majority of the operators in this table will probably be incomprehensible to you right now. That's expected at this point. We'll cover many of them in this chapter, and the rest will be introduced as there is a need for them.
Q: Where is the exponent operator?
C++ doesn't include an operator to do exponentiation (operator^ has a different function in C++). We discuss exponentiation more in the Remainder and Exponentiation lesson.
Note that operator<< handles both bitwise left shift and insertion, and operator>> handles both bitwise right shift and extraction. The compiler can determine which operation to perform based on the types of the operands.
Parenthesization
Due to the precedence rules, 6 + 3 * 4 will be grouped as 6 + (3 * 4). But what if we actually meant (6 + 3) * 4? Just like in normal mathematics, in C++ we can explicitly use parentheses to set the grouping of operands as we desire. This works because parentheses have one of the highest precedence levels, so parentheses generally evaluate before whatever is inside them.
Use parenthesis to make compound expressions easier to understand
Now consider an expression like playerScore && enemyScore || bonusActive. Does this evaluate as (playerScore && enemyScore) || bonusActive or playerScore && (enemyScore || bonusActive)? You could look up in the table and see that && takes precedence over ||. But there are so many operators and precedence levels that it's hard to remember them all. And you don't want to have to look up operators all the time to understand how a compound expression evaluates.
In order to reduce mistakes and make your code easier to understand without referencing a precedence table, it's a good idea to parenthesize any non-trivial compound expression, so it's clear what your intent is.
Use parentheses to make it clear how a non-trivial compound expression should evaluate (even if they are technically unnecessary).
A good rule of thumb is: Parenthesize everything, except addition, subtraction, multiplication, and division.
There is one additional exception to the above best practice: Expressions that have a single assignment operator (and no comma operator) do not need to have the right operand of the assignment wrapped in parenthesis.
For example:
totalScore = (playerScore + bonusScore + comboMultiplier); // instead of this
totalScore = playerScore + bonusScore + comboMultiplier; // it's okay to do this
totalScore = ((playerWon || enemyDefeated) && levelComplete); // instead of this
totalScore = (playerWon || enemyDefeated) && levelComplete; // it's okay to do this
totalScore = (playerScore *= bonusMultiplier); // expressions with multiple assignments still benefit from parenthesis
The assignment operators have the second lowest precedence (only the comma operator is lower, and it's rarely used). Therefore, so long as there is only one assignment (and no commas), we know the right operand will fully evaluate before the assignment.
Expressions with a single assignment operator do not need to have the right operand of the assignment wrapped in parenthesis.
Value computation of operations
The C++ standard uses the term value computation to mean the execution of operators in an expression to produce a value. The precedence and association rules determine the order in which value computation happens.
For example, given the expression 6 + 3 * 4, due to the precedence rules this groups as 6 + (3 * 4). The value computation for (3 * 4) must happen first, so that the value computation for 6 + 12 can be completed.
Evaluation of operands
The C++ standard (mostly) uses the term evaluation to refer to the evaluation of operands (not the evaluation of operators or expressions!). For example, given expression playerScore + bonusScore, playerScore will be evaluated to produce some value, and bonusScore will be evaluated to produce some value. These values can then be used as operands to operator+ for value computation.
Nomenclature: Informally, we typically use the term "evaluates" to mean the evaluation of an entire expression (value computation), not just the operands of an expression.
The order of evaluation of operands (including function arguments) is mostly unspecified
In most cases, the order of evaluation for operands and function arguments is unspecified, meaning they may be evaluated in any order.
Consider the following expression:
playerScore * bonusMultiplier + enemyScore * penaltyMultiplier
We know from the precedence and associativity rules above that this expression will be grouped as if we had typed:
(playerScore * bonusMultiplier) + (enemyScore * penaltyMultiplier)
If playerScore is 100, bonusMultiplier is 2, enemyScore is 50, and penaltyMultiplier is 3, this expression will always compute the value 350.
However, the precedence and associativity rules only tell us how operators and operands are grouped and the order in which value computation will occur. They do not tell us the order in which the operands or subexpressions are evaluated. The compiler is free to evaluate operands playerScore, bonusMultiplier, enemyScore, or penaltyMultiplier in any order. The compiler is also free to calculate playerScore * bonusMultiplier or enemyScore * penaltyMultiplier first.
For most expressions, this is irrelevant. In our sample expression above, it doesn't matter in which order variables playerScore, bonusMultiplier, enemyScore, or penaltyMultiplier are evaluated for their values: the value calculated will always be 350. There is no ambiguity here.
But it is possible to write expressions where the order of evaluation does matter. Consider this program, which contains a mistake often made by new C++ programmers:
#include <iostream>
int getScore()
{
std::cout << "Enter a score: ";
int score {};
std::cin >> score;
return score;
}
void printTotal(int score1, int score2, int score3)
{
std::cout << score1 + (score2 * score3);
}
int main()
{
printTotal(getScore(), getScore(), getScore()); // this line is ambiguous
return 0;
}
If you run this program and enter the inputs 10, 20, and 30, you might assume that this program would calculate 10 + (20 * 30) and print 610. But that is making the assumption that the arguments to printTotal() will evaluate in left-to-right order (so parameter score1 gets value 10, score2 gets value 20, and score3 gets value 30). If instead, the arguments evaluate in right-to-left order (so parameter score3 gets value 10, score2 gets value 20, and score1 gets value 30), then the program will print 630 instead.
Tip: The Clang compiler evaluates arguments in left-to-right order. The GCC compiler evaluates arguments in right-to-left order.
If you'd like to see this behavior for yourself, you can do so on Wandbox. Paste in the above program, enter 10 20 30 in the Stdin tab, select GCC or Clang, and then compile the program. The output will appear at the bottom of the page (you may have to scroll down to see it). You will note that the output for GCC and Clang differs!
The above program can be made unambiguous by making each function call to getScore() a separate statement:
#include <iostream>
int getScore()
{
std::cout << "Enter a score: ";
int score {};
std::cin >> score;
return score;
}
void printTotal(int score1, int score2, int score3)
{
std::cout << score1 + (score2 * score3);
}
int main()
{
int firstScore { getScore() }; // will execute first
int secondScore { getScore() }; // will execute second
int thirdScore { getScore() }; // will execute third
printTotal(firstScore, secondScore, thirdScore); // this line is now unambiguous
return 0;
}
In this version, firstScore will always have value 10, secondScore will have value 20, and thirdScore will have value 30. When the arguments to printTotal() are evaluated, it doesn't matter which order the argument evaluation happens in -- parameter score1 will always get value 10, score2 will get value 20, and score3 will get value 30. This version will deterministically print 610.
Operands, function arguments, and subexpressions may be evaluated in any order.
It is a common mistake to believe that operator precedence and associativity affects order of evaluation. Precedence and associativity is used only to determine how operands are grouped with operators, and the order of value computation.
Ensure that the expressions (or function calls) you write are not dependent on operand (or argument) evaluation order.
Related content: Operators with side effects can also cause unexpected evaluation results. We cover this in the Increment/decrement operators, and side effects lesson.
Summary
- Operator precedence: All operators are assigned a precedence level; operators with higher precedence are grouped with operands first
- Operator associativity: When operators have the same precedence level, associativity determines whether they evaluate left-to-right or right-to-left
- Parentheses: Have one of the highest precedence levels; use them to explicitly control grouping in compound expressions
- Best practices:
- Parenthesize non-trivial compound expressions to make intent clear
- Rule of thumb: Parenthesize everything except addition, subtraction, multiplication, and division
- Expressions with a single assignment don't need parentheses around the right operand
- Value computation: The execution of operators to produce a value; order is determined by precedence and associativity
- Operand evaluation: The evaluation of operands happens separately from value computation
- Unspecified evaluation order: In most cases, the order in which operands and function arguments are evaluated is unspecified and may vary between compilers
- Compiler differences: Clang evaluates function arguments left-to-right, GCC evaluates right-to-left
- Common mistake: Assuming precedence/associativity determines operand evaluation order (it doesn't - it only determines grouping)
- Safe coding: Avoid writing expressions or function calls that depend on the order of operand or argument evaluation
Understanding operator precedence and associativity helps you write clear, unambiguous expressions. When in doubt, use parentheses to make your intent explicit rather than relying on implicit precedence rules.
Understanding Operator Precedence Rules - Quiz
Test your understanding of the lesson.
Practice Exercises
Operator Precedence Quiz
Predict and verify the results of expressions involving operator precedence and associativity.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!