Compound Assignment Operators
Operator overloading redefines operators for user-defined types by writing an ordinary function whose name is the keyword operator followed by the symbol.
Why Programs Update Their Own Values
Many programs change a variable based on its own current value. A counter goes up by one, a running total grows by some amount, and a number gets scaled. Each of these updates reads the variable, combines it with something, and stores the result back in the same variable.
Besides the usual arithmetic and logical operators, C++ provides operators written specifically for modifying a variable in place. These are compound assignment operators. They are concise, convenient, and very frequently used, so once you can read them you will see them in a large share of real code. As you work through this article, keep one question in mind: when a statement changes a variable, which value is read, and which value is written back?
Following One Variable Through Three Statements
Before defining anything, watch a variable change. The program below is a small illustration written for this article. It is not the program behind the verified run described later. It starts a variable x at 10, adds 5 to it with the += operator, multiplies it by 2 with the *= operator, and prints x after each change.
15
30- Line 4 sets the starting value of x to 10.
- Line 5 runs x += 5, which updates x to 15.
- Line 6 prints the first line of output, 15.
- Line 7 runs x *= 2, which updates x to 30.
- Line 8 prints the second line of output, 30.
Read the trace from top to bottom. The first printed line is 15 because the statement x += 5 ran before the first output statement. The second printed line is 30 because x *= 2 ran before the second output statement. Each printed value comes from the most recent statement that changed x. This matching of output lines to statements is the skill you will use on longer programs.
The source's own verified run printed five lines: 15, 12, 24, 6.0, and 2.0. The listing that produced those lines is not reproduced in the material this article is built from, so this article does not attempt to reconstruct it. The approach you just used, following the value through each statement, is the same approach you would apply to that run.
What a Compound Assignment Does
A compound assignment combines an operator with the equals sign, so that one statement both computes a new value and stores it back into the same variable. The forms the source lists are x += y, x -= y, x *= y, x /= y, and x %= y. The source also lists the increment and decrement forms ++x and --x.
- x += y adds y to x
- x -= y subtracts y from x
- x *= y multiplies x by y
- x /= y divides x by y
- x %= y takes the remainder of x divided by y
- ++x and --x are the increment and decrement forms
The source defines x += y as x = x + y. The same rule applies to the other operators: replace the plus sign with minus, asterisk, slash, or percent, and the reading stays the same. The variable on the left is updated using its own current value and the value on the right. To read any compound statement, rewrite it in the form x = x op y, where op stands for the operator, and then the variable on the left is clearly updated from its current value.
Notice that the variable on the left appears on both sides of the rewritten form. That is why the statement changes the variable: it is read first, combined with y, and then the result replaces the old value. In the trace earlier, x += 5 read 10, combined it with 5, and stored 15 back into x.
Reading an Overloaded Operator Signature
Operator overloading redefines operators for user-defined types. An overloaded operator is an ordinary function whose name is the keyword operator followed by the symbol. Built-in types such as integers and floating-point numbers already know what + and == mean. A complex number is different: it has a real part and an imaginary part, so a program that stores complex numbers in its own type needs a way to use ordinary operator symbols on them. Overloading lets an expression like a + b be carried out by a function written for that type.
The first definition in the source is the addition operator. Its name is operator+, it returns a complex value, and it takes two complex parameters called a and b. Its body is a single statement that returns a += b. The sketch below follows that pattern. It is an illustration written for this article, not the source's exact class listing; it assumes the class already defines the compound operator += as a member, as the pattern requires.
complex operator+(complex a, complex b) {
return a += b;
}- 1The function receives two complex values, a and b.
- 2The statement a += b runs the compound assignment on a, updating a with the sum of a and b.
- 3The return statement sends that updated value back to the caller.
- 4The arithmetic rules live inside += on the class, so this function stays a thin wrapper.
- The return type is complex, so the function hands back a complex value.
- The name operator+ is the keyword operator followed by the plus symbol.
- The parameter list (complex a, complex b) has two parameters, so this is the binary form.
- The body reuses the compound assignment on the first parameter and returns the result.
The source defines four binary arithmetic operators in the same pattern: addition, subtraction, multiplication, and division. Each takes two complex values, returns a complex value, and uses the matching compound assignment on the first parameter. Subtraction uses a -= b, multiplication uses a *= b, and division uses a /= b. Once a type has the compound assignment operators, each matching binary operator can be written in one line.
Binary and Unary Forms of the Same Symbol
The source also defines a unary minus, which negates a single value. It uses the same symbol as binary subtraction, and this is the central point of the section. The compiler does not choose between the two forms by name, because both are named operator-. The difference is the parameter list: the binary form takes two complex parameters, and the unary form takes one.
Read the two columns side by side. Both use the same operator name, and neither refers to the other. A reader who sees only the symbol cannot tell which function runs. A reader who counts the parameters can. This is the core rule of overloading in this source: one operator symbol can have several definitions, and each definition is a different function selected by its signature.
Which operator- runs?
Given complex values z and w, which definition of operator- runs for the expression z - w, and which runs for the expression -z?
- 1Look at z - w
Two operands appear on either side of the minus sign, so the call passes two complex values.
- 2Match to the parameter list
The binary form takes two parameters, a and b, so it is the version that runs for z - w.
- 3Look at -z
Only one operand appears after the minus sign, so the call passes one complex value.
- 4Match to the parameter list
The unary form takes one parameter, so it is the version that runs for -z, and it returns a new complex value built from the negated parts of z.
z - w runs the binary operator- that computes a -= b on a copy of z. -z runs the unary operator- that builds a new complex value from the negated real and imaginary parts.
Tracing an Equality Check on Complex Values
The second group of definitions covers comparison. The equality operator returns a bool, and it is true only when both parts agree: the real parts are equal and the imaginary parts are equal. The source joins the two tests with the logical and operator, so both must succeed for the result to be true. The sketch below is an illustration in the same style as the source's description, not its exact listing.
- operator== compares the real parts and the imaginary parts separately.
- The && requires both comparisons to be true.
- operator!= is defined by calling operator== and negating the result, so the equality logic lives in one place.
Checking 2 plus 3i against 2 plus 5i
Two complex values share a real part of 2 but have imaginary parts of 3 and 5. Does a == b return true?
- 1Identify the operands
a is 2 plus 3i, so its real part is 2 and its imaginary part is 3. b is 2 plus 5i, so its real part is 2 and its imaginary part is 5.
- 2Compare the real parts
a.real()==b.real()becomes 2 == 2, which is true. - 3Compare the imaginary parts
a.imag()==b.imag()becomes 3 == 5, which is false. - 4Combine with &&
true && false is false, so the whole equality test returns false.
a == b returns false. The real parts agree, but the imaginary parts do not, so the values are not equal.
When a result looks wrong, trace it through the definition one step at a time and note the exact expression where your expectation and the code part ways. Here the first comparison matches your intuition, and the second comparison is where the result is decided. Because operator!= is written in terms of operator==, a change to the meaning of equality changes not-equal too. Separate comparison code in each operator could drift apart and disagree.
Mistakes When Reading Compound Assignment Operators
Common mistake Reading x += y as if x were not part of the calculation
What it looks likeReading x += 5 as simply storing 5 into x.
Why it failsThe source defines x += y as x = x + y, so the current value of x is read and combined with y before the result is written back.
Do this insteadRewrite the statement as x = x + 5 and identify the value x held before the statement ran.
Common mistake Choosing an overloaded operator by its symbol alone
What it looks likeAssuming every use of minus calls the binary subtraction function.
Why it failsThe same symbol can name a binary form and a unary form, and the parameter list selects which function runs.
Do this insteadCount the operands at the call site and match that count to the parameter list of each definition.
Common mistake Assuming the caller's object changes inside an overloaded operator
What it looks likeExpecting a to be altered in the caller after calling operator+ on a and b.
Why it failsThe parameters are passed by value, so the function works on copies and returns the result.
Do this insteadLook at the returned value to find the result, and do not expect the caller's object to change through the call.
Common mistake Looking for separate logic in not-equal
What it looks likeSearching for a second comparison body in operator!= and not finding one.
Why it failsThe source defines not-equal as the negation of equal, so the decision is made in one place.
Do this insteadTrace not-equal into the equality operator and reason from there.
Common mistake Matching printed output to the wrong statement, instead of tracing the value line by line through each change.
Predicting a Run of Compound Assignments
What do you think happens?
Before reading the answer, predict what this program prints: int x = 4; x += 3; x *= 2; x -= 1; then print x.
Reveal answer
13
x starts at 4. x += 3 reads 4, adds 3, and stores 7. x *= 2 reads 7, multiplies by 2, and stores 14. x -= 1 reads 14, subtracts 1, and stores 13. The final value printed is 13.
Rewrite each compound assignment as the longer form x = x op y. Then, for total -= discount, state which value is read from total and which value is written back into total.
Hints
- Keep the variable on the left, repeat it on the right, and put the operator between the two.
- For the rewrite of a *= b, the result is a = a * b.
- The value read from total is its value before the statement ran, and the value written back is the difference.
Key Takeaways
- A compound assignment such as x += y updates a variable in one statement, and the source defines it as x = x + y: the current value of x is read, combined with y, and stored back into x.
- To trace a program that uses compound assignments, follow the value of each variable through each statement and match every printed line to the most recent statement that changed the value it shows.
- Operator overloading defines an operator for a user-defined type as an ordinary function named operator followed by the symbol, with a return type, a parameter list, and a body.
- One operator symbol can have several overloads, such as binary and unary minus; the parameter list determines which function runs.
- In the source's complex-number examples, equality compares the real and imaginary parts with &&, and not-equal is defined as the negation of equality, so a wrong result can be traced to one specific comparison.