6.1 — Operator precedence and associativity – Learn C++

Content

Chapter introduction

This chapter builds on top of the concepts from lesson 1.9 -- Introduction to literals and operators. 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 2 + 3 equals 5. In this case, the literals 2 and 3 are the operands, and the symbol + is the operator that tells us to apply mathematical addition on the operands to produce the new value 5. 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 4 + 2 * 3. Should this be grouped as (4 + 2) * 3 which evaluates to 18, or 4 + (2 * 3) which evaluates to 10? Using normal mathematical precedence rules (which state that multiplication is resolved before addition), we know that the above expression should be grouped as 4 + (2 * 3) to produce the value 10. 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, 4 + 2 * 3 will be grouped as 4 + (2 * 3).

Operator associativity

Consider a compound expression like 7 - 4 - 1. Should this be grouped as (7 - 4) - 1 which evaluates to 2, or 7 - (4 - 1), which evaluates to 4? Since both subtraction operators have the same precedence level, the compiler can not 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: (7 - 4) - 1.

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)
Namespace scope (binary)

::nameclass_name::member_name

2 L->R

()()type()type{}[].->++––typeidconst_castdynamic_castreinterpret_caststatic_castsizeof…noexcept

'alignof'

ParenthesesFunction callFunctional castList init temporary object (C++11)Array subscriptMember access from objectMember access from object ptrPost-incrementPost-decrementRun-time type informationCast away constRun-time type-checked castCast one type to anotherCompile-time type-checked castGet parameter pack sizeCompile-time exception check

Get type alignment

(expression)function_name(arguments)type(expression)type{expression}pointer[expression]object.member_nameobject_pointer->member_namelvalue++lvalue––typeid(type) or typeid(expression)const_cast<type>(expression)dynamic_cast<type>(expression)reinterpret_cast<type>(expression)static_cast<type>(expression)sizeof…(expression)noexcept(expression)

alignof(type)

3 R->L

+-++––!not~(type)sizeofco_await&*newnew[]delete

delete[]

Unary plusUnary minusPre-incrementPre-decrementLogical NOTLogical NOTBitwise NOTC-style castSize in bytesAwait asynchronous callAddress ofDereferenceDynamic memory allocationDynamic array allocationDynamic memory deletion

Dynamic array deletion

+expression-expression++lvalue––lvalue!expressionnot expression~expression(new_type)expressionsizeof(type) or sizeof(expression)co_await expression (C++20)&lvalue*expressionnew typenew type[expression]delete pointer

delete[] pointer

4 L->R

->* .*

Member pointer selector Member object selector

object_pointer->*pointer_to_member
object.*pointer_to_member

5 L->R

*/

%

MultiplicationDivision

Remainder

expression * expressionexpression / expression

expression % expression

6 L->R

Addition Subtraction

expression + expression
expression - expression

7 L->R

<< \>>

Bitwise shift left / Insertion Bitwise shift right / Extraction

expression << expression expression >> expression

8 L->R

<=>

Three-way comparison (C++20)

expression <=> expression

9 L->R

<<=>

=

Comparison less thanComparison less than or equalsComparison greater than

Comparison greater than or equals

expression < expressionexpression <= expressionexpression > expression

expression >= expression

10 L->R

 !=

Equality Inequality

expression == expression
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

&& and

Logical AND
Logical AND

expression && expression
expression and expression

15 L->R

||
or

Logical OR
Logical OR

expression || expression expression or expression

16 R->L

throwco_yield?:=*=/=%=+=-=<<=>>=&=|=

^=

Throw expressionYield expression (C++20)ConditionalAssignmentMultiplication assignmentDivision assignmentRemainder assignmentAddition assignmentSubtraction assignmentBitwise shift left assignmentBitwise shift right assignmentBitwise AND assignmentBitwise OR assignment

Bitwise XOR assignment

throw expressionco_yield expressionexpression ? expression : expressionlvalue = expressionlvalue *= expressionlvalue /= expressionlvalue %= expressionlvalue += expressionlvalue -= expressionlvalue <<= expressionlvalue >>= expressionlvalue &= expressionlvalue |= expression

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 lesson 6.3 -- Remainder and Exponentiation.

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, 4 + 2 * 3 will be grouped as 4 + (2 * 3). But what if we actually meant (4 + 2) * 3? 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 x && y || z. Does this evaluate as (x && y) || z or x && (y || z)? 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.

Best practice

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:

x = (y + z + w);   // instead of this
x = y + z + w;     // it's okay to do this
x = ((y || z) && w); // instead of this
x = (y || z) && w;   // it's okay to do this
x = (y *= z); // 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.

Best practice

Expressions with a single assignment operator do not need to have the right operand of the assignment wrapped in parentheses.

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 4 + 2 * 3, due to the precedence rules this groups as 4 + (2 * 3). The value computation for (2 * 3) must happen first, so that the value computation for 4 + 6 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 a + b, a will be evaluated to produce some value, and b will be evaluated to produce some value. These values can be then 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:

a * b + c * d

We know from the precedence and associativity rules above that this expression will be grouped as if we had typed:

(a * b) + (c * d)

If a is 1, b is 2, c is 3, and d is 4, this expression will always compute the value 14.

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 a, b, c, or d in any order. The compiler is also free to calculate a * b or c * d first.

For most expressions, this is irrelevant. In our sample expression above, it doesn’t matter in which order variables a, b, c, or d are evaluated for their values: the value calculated will always be 14. 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>

Summary
This chapter builds on the concepts of literals and operators in C++. It defines operations as mathematical processes involving operands and operators, exemplified by the expression `2 + 3 = 5`. The chapter discusses the evaluation of compound expressions, such as `4 + 2 * 3`, emphasizing the importance of operator precedence and associativity in determining how expressions are grouped and evaluated. Higher precedence operators, like multiplication, are resolved before lower precedence ones, such as addition. The chapter introduces a table of operator precedence and associativity, which serves as a reference for understanding how different operators interact. It also highlights the significance of parentheses in clarifying the intended grouping of operands, especially in complex expressions. Best practices suggest using parentheses to enhance code readability, even when they are not strictly necessary. The chapter concludes by noting that C++ does not include an exponentiation operator, and it encourages developers to parenthesize non-trivial expressions to avoid ambiguity and improve code clarity.