Historical Context & Motivation
The increment and decrement operators are among the most iconic syntactic features of the C family of languages, and their origin is tightly intertwined with the hardware culture of early computing. In the late 1960s, Ken Thompson and Dennis Ritchie were developing the B and C programming languages at Bell Labs on a PDP-7 and later a PDP-11 minicomputer. The PDP-11 instruction set included auto-increment and auto-decrement addressing modes that could adjust a register's value before or after it was used as an address, allowing a single machine instruction to both access memory and bump a pointer. Thompson recognized that exposing this hardware capability directly through language syntax would yield tighter, more efficient compiled code, and so the ++ and -- operators — in both prefix and postfix forms — became first-class citizens of the B language and then of C.
++ and -- in the B language, directly motivated by the PDP-7's auto-increment register hardware.int parameter to distinguish prefix from postfix overloads.Even though modern compilers can optimize away the hardware-level distinction in many contexts, the semantic difference between prefix and postfix forms remains critically important. A misunderstanding of when the modification occurs relative to the value used in the expression continues to be a common source of bugs in both student and professional code. This lesson dissects exactly how each form works, so that you can use them with precision and confidence.
Core Principles & Definitions
The increment and decrement operators are unary operators — they take a single operand, which must be a modifiable lvalue (an expression that refers to a memory location capable of being assigned to). The operators exist in two syntactic forms: prefix (++x / --x) and postfix (x++ / x--). Both forms have the same side effect — they add or subtract 1 from the operand — but they differ in the value they yield to the surrounding expression.
Prefix Increment (++x)
Postfix Increment (x++)
Prefix Decrement (--x)
Postfix Decrement (x--)
Operand Requirement: Modifiable Lvalue
5++ or ++(a+b) because literals and temporaries are not modifiable lvalues.Visual Explanation — Prefix vs. Postfix in Action
The diagram above makes the central distinction concrete. In the prefix case (int y = ++x;), the compiler conceptually performs two operations in sequence: first it increments x from 5 to 6, then it evaluates the expression ++x as 6 and stores that in y. In the postfix case (int y = x++;), the expression x++ evaluates to the original value 5, which is assigned to y, and then x is incremented to 6 as a side effect. After both statements, x holds 6 in either case — the only difference is what y received.
How It Works — Evaluation Semantics
Understanding the operators deeply requires thinking about them in terms of value categories and sequencing. In the C++ standard, the prefix forms are defined to return an lvalue reference to the modified operand, while the postfix forms return a prvalue copy of the operand's original value. This difference has practical consequences: because prefix returns an lvalue, you can chain operations like ++++x (incrementing twice), whereas x++++ is ill-formed because the first postfix returns a prvalue, which is not a modifiable lvalue.
x is incremented, and the expression evaluates to the new value. The result is an lvalue — a reference to x itself.temp captures the original value of x. Then x is incremented. The expression yields temp — a prvalue (an rvalue temporary).i = i++ + ++i; is undefined behavior. Never modify a variable more than once in the same full-expression unless there is a sequencing guarantee (such as the comma operator, &&, ||, or ?:).Detailed Breakdown — Pre vs. Post in Context
To solidify the distinction, let us trace through several common usage patterns. The difference between prefix and postfix is most visible when the operator's result is used — for instance, assigned to another variable, passed as a function argument, or embedded in a compound expression. When the result is discarded (as in a standalone statement x++; or ++x;), the observable effect is identical for built-in types.
x = 3. Prefix forms yield the modified value; postfix forms yield the original value. The bottom panels illustrate that in loop updates the choice is stylistic, but in array indexing the choice is semantically critical.| Feature | Prefix (++x / --x) | Postfix (x++ / x--) |
|---|---|---|
| Value yielded | New (after modification) | Old (before modification) |
| Value category of result | lvalue | prvalue |
| Can be further modified? | Yes (e.g., ++++x) | No — result is a temporary |
| Performance for iterators | Potentially faster — no copy needed | Creates a copy of the original |
| Typical idiomatic use | Loop updates, "increment then use" | "Use then increment" (e.g., arr[i++]) |
Worked Example — Tracing Through Code
Let us trace through a small program that uses both prefix and postfix operators in different contexts. The goal is to predict the exact console output.
#include <iostream>
int main() {
int a = 5;
int b = a++; // Line 1
int c = ++a; // Line 2
int d = --c + b--; // Line 3
std::cout << a << " " << b << " " << c << " " << d << std::endl;
return 0;
}a before incrementing. Since a starts at 5, b receives 5. Then a is incremented to 6.a first (from 6 to 7), then yields the new value. So c receives 7.--c is prefix decrement: c goes from 7 to 6, and the expression yields 6. b-- is postfix decrement: the expression yields the old value of b (5), and then b is decremented to 4. The addition is 6 + 5 = 11.a << " " << b << " " << c << " " << d gives us the final state of all four variables.7 4 6 11Common Idioms and Pitfalls
Knowing the rules is only half the battle; knowing when to prefer each form is equally important. The C++ community has developed strong conventions around increment and decrement operators that balance readability, correctness, and (for user-defined types) performance.
| Scenario | Recommended Form | Rationale |
|---|---|---|
| Loop counter update (result discarded) | ++i | Convention; avoids unnecessary copy for iterators and user-defined types. |
| "Use current value, then advance" | i++ | Postfix is the natural choice when you need the old value (e.g., *p++ in pointer idioms). |
| Standalone statement | Either (prefer ++i) | For built-in types, compilers optimize both identically. Prefix is the community default. |
| Complex expression with multiple side effects | Avoid | Mixing increment/decrement of the same variable in one expression risks undefined behavior. |
| Iterator traversal in STL | ++it | Postfix requires copying the iterator. For complex iterators (e.g., std::map::iterator), prefix can be measurably faster. |
Connection to Operator Overloading
For built-in types, the compiler handles increment and decrement natively. However, one of C++'s distinctive features is operator overloading, which allows user-defined types (classes) to provide custom implementations of ++ and --. The C++ standard distinguishes the two overloads via a dummy int parameter in the postfix signature. Understanding this convention is essential when you move on to implementing custom iterators or smart counters.
| Aspect | Built-in Types (int, double*, etc.) | User-Defined Types (Classes) |
|---|---|---|
| Prefix signature | Handled by compiler intrinsic | T& operator++() |
| Postfix signature | Handled by compiler intrinsic | T operator++(int) — the int is a dummy parameter |
| Return type | Prefix: lvalue; Postfix: prvalue | Convention mirrors built-in: prefix returns T&, postfix returns T |
| Performance difference | Typically optimized away | Postfix constructs a copy — can be significant for complex types |
| When to learn | Now (this lesson) | After classes, operator overloading, and iterators |
As you advance to topics like STL iterators, move semantics, and custom container design, the prefix-vs.-postfix distinction will become even more consequential. The postfix overload for a heavyweight iterator might involve copying an entire state machine, making ++it meaningfully faster than it++. This is why the modern C++ guideline is clear: prefer prefix unless you specifically need the old value.
Practice Problems
++x returns an lvalue while x++ returns a prvalue. Why does this distinction matter for chaining operations?x, y, and z?
int x = 10;
int y = x--;
int z = --x;int a = 3;
int b = ++a * a++;
std::cout << a << " " << b << std::endl;
Hint: consider the C++ sequencing rules.N and a write index writeIdx. Write a single expression that stores a value val at the current write position and then advances writeIdx to the next slot (wrapping around). Use the appropriate form of the increment operator.BigInt that represents arbitrarily large integers using a std::vector<uint8_t> of digits. Discuss why the performance difference between prefix and postfix ++ could be significant for this type, and sketch the signatures and key implementation differences of the two overloaded operators.Lesson Summary
C++ provides four unary operators for adjusting a variable by one: prefix increment (++x), postfix increment (x++), prefix decrement (--x), and postfix decrement (x--). All four share the same side effect — adding or subtracting 1 — but they differ critically in the value they yield to the surrounding expression. Prefix forms modify the operand first and return the new value as an lvalue; postfix forms capture the old value as a prvalue and then apply the modification.
When the expression result is discarded (e.g., in a for-loop update), both forms produce identical observable behavior for built-in types, but the C++ community convention is to prefer prefix because it avoids creating unnecessary temporaries — a consideration that becomes performance-relevant for user-defined types and iterators. Finally, never modify the same variable more than once in a single expression without guaranteed sequencing, as doing so invokes undefined behavior.