C++ / OPERATORS AND EXPRESSIONS
Assignment and compound assignment
Read and write assignment and compound assignments, predict the conversion back to the left type, and know when += avoids repeated work.
What you will learn
- Tell initialization from assignment: the `=` in `int x = 5;` is not the assignment operator.
- Rewrite `x = x op y` as `x op= y` and explain the single-evaluation guarantee.
- Predict what `i += 0.4;` does to an `int`, and why such a counter never advances.
- Chain `a = b = 0` and use the value an assignment expression yields.
Understanding Assignment and compound assignment
Assignment in C++ is an operator, not a statement: `x = 7` is an expression whose value is `x` itself, as a modifiable lvalue. That is why `a = b = c = 10;` works. `=` groups right to left, so `c = 10` runs first, the result is the freshly updated `c`, and that value flows left into `b` and then `a`. The `=` in `int x = 5;` is a different thing entirely: it is initialization syntax, so the variable is built holding 5 rather than being handed a value it already had. For class types that distinction decides whether a constructor runs or an assignment operator runs, and only the assignment operator has to deal with an existing value it must discard.
Compound assignment `x op= y` means `x = x op y` with two deliberate differences. First, the left operand is evaluated exactly once, so `counts[hash(key)] += 1` computes that subscript once where the long form computes it twice, which matters as soon as the subexpression calls a function or walks a pointer. Second, the arithmetic happens after the usual arithmetic conversions and the result is then converted back to the type of the left operand, even when that loses information: `int m = 5; m /= 2.0;` computes 2.5 as a `double` and stores 2. The truncation is part of what `/=` means there, not a stray bug, which is why adding a fractional amount into an `int` counter can move it by nothing at all.
For class types none of this is built in; `operator+=` and `operator=` are functions someone wrote. The convention is that `+=` mutates the object in place while `a + b` builds a new object that then has to be assigned, so `s += "local"` appends into the buffer the string already owns while `s = s + "local"` constructs a temporary, moves it in, and destroys it. That is why the compound form is the default for strings and any other type whose copies cost something. C++ also has no `&&=` or `||=`: the set is `+= -= *= /= %= &= |= ^= <<= >>=`, and a `&&=` is absent because it would have to skip evaluating its right operand sometimes, which is not how the other compound operators behave.
<iostream>
<string>
int main() {
int a = 0, b = 0, c = 0; // the = here is initialization, not assignment
a = b = c = 10; // right to left: c = 10, then b = c, then a = b
std::cout << a << ' ' << b << ' ' << c << '\n';
int n = 7;
n += 3; // 10
n *= 2; // 20
n /= 3; // still integer division: 6
std::cout << "n = " << n << '\n';
int m = 5;
m /= 2.0; // 5 / 2.0 is 2.5, converted back to int
std::cout << "m = " << m << '\n';
std::string s = "log";
s += ':';
s += " ready";
std::cout << s << '\n';
int k = 4;
int r = (k += 6); // the whole expression yields k after the update
std::cout << "r = " << r << ", k = " << k << '\n';
}
Compound assignment is `x = x op y` with the left side evaluated only once and the result converted back to the left side's type.
Worked examples
The left side is evaluated once
Counts how many times a subexpression on the left runs under `+=` versus the spelled-out form.
<iostream>
int calls = 0;
int slot() {
++calls;
return 1;
}
int main() {
int data[3] = {0, 0, 0};
data[slot()] += 5;
std::cout << "compound: calls = " << calls << ", data[1] = " << data[1] << '\n';
calls = 0;
data[slot()] = data[slot()] + 5;
std::cout << "long : calls = " << calls << ", data[1] = " << data[1] << '\n';
}
Example explained
Line 1`data[slot()] += 5;` names the target once, so `slot()` is called once and the counter reaches 1.
Line 2`data[slot()] = data[slot()] + 5;` names it twice, so `slot()` runs twice; that is wasted work here and an outright bug if the second call returned a different index.
Line 3`data[1]` ends at 10 because the second statement adds 5 to the 5 the first one left behind.
Conversion back to the left operand's type
Shows the result of the arithmetic being forced back into the type of the variable on the left.
<iostream>
int main() {
int score = 9;
score += 0.9;
std::cout << "score = " << score << '\n';
double avg = 7;
avg /= 2;
std::cout << "avg = " << avg << '\n';
unsigned char level = 250;
level += 10;
std::cout << "level = " << static_cast<int>(level) << '\n';
}
Example explained
Line 1`score += 0.9` promotes `score` to `double`, computes 9.9, then truncates on the way back into the `int`, so the variable does not move.
Line 2`avg /= 2` converts the `int` 2 to `double`, so 3.5 survives: the conversion back to the left type changes nothing here.
Line 3`level += 10` does the addition as `int` (260) and then converts to `unsigned char`, which wraps modulo 256 down to 4.
Line 4The cast in the last line only affects printing; without it `std::cout` would treat the value as a character.
+= and = on a class type
Distinguishes initialization from assignment for `std::string`, and shows `+=` appending in place.
<iostream>
<string>
int main() {
std::string path = "usr";
path += '/';
path += "local";
std::cout << path << '\n';
std::string other = path;
other = "elsewhere";
std::cout << path << " | " << other << '\n';
}
Example explained
Line 1`std::string path = "usr";` is initialization: a constructor builds the object, and no assignment operator is called.
Line 2`path += '/'` calls the member `operator+=` for a single `char`, appending into the buffer the string already owns.
Line 3`std::string other = path;` is also initialization, so the copy constructor runs, not `operator=`.
Line 4`other = "elsewhere";` is the real assignment operator, which must throw away the old contents; `path` is a separate object and is untouched.
Important notes
C++ has no `&&=` or `||=`; the compound operators are `+= -= *= /= %= &= |= ^= <<= >>=`.
Since C++17 the right operand of an assignment is sequenced before the left, so in `v[i()] = f()` the call to `f()` happens first; earlier standards left that order unspecified.
Common mistakes
Typing `if (ready = 0)` when `if (ready == 0)` was meant: the assignment stores 0, the condition tests that new value, so the branch never runs and the old value of `ready` is destroyed.
Expecting `total += 0.4` to accumulate in an `int total`: each step computes 0.4 and truncates back to 0, so the counter never advances and the loop looks broken for no visible reason.
Slipping the space in `x =+ 1` or `total =- 5`: these parse as `x = +1` and `total = -5`, overwriting the variable instead of adjusting it, and the compiler has nothing to object to.
Try it yourself
Change, predict, then run
Start from `int cents = 195;` and, using only compound assignments, double it, add 5, then reduce it to the remainder after dividing by 100, printing `cents` after each step. Then append `cents *= 1.5;` and explain the number you get.
Open the C++ workspaceCheck your understanding
Given `int i = 3;` followed by `i += 1.8;`, what does `i` hold afterwards and why?
- 4, because the sum is computed as a double (4.8) and then converted back to int, which discards the fraction
- 5, because the double result 4.8 is rounded to the nearest int on the way back into i
- 4.8, because += promotes i to double for the rest of the program
- Nothing usable, because the line is ill-formed: += requires both operands to have the same type
Show answer
Compound assignment does its arithmetic after the usual arithmetic conversions, so 3 becomes 3.0 and the sum is 4.8, and the operator's definition then converts that result back to the left operand's type. Converting a floating value to an integer type truncates toward zero, so 4.8 becomes 4. The rounding-to-5 answer is tempting because many rounding helpers work that way, but the language's built-in conversion never rounds, and the type of `i` is fixed at compile time, so it cannot become a double.