4.5. Increment and Decrement Operators
The increment (++
) and decrement (--
) operators provide a convenient notational shorthand for adding or subtracting 1 from an object. This notation rises above mere convenience when we use these operators with iterators, because many iterators do not support arithmetic.
There are two forms of these operators: prefix and postfix. So far, we have used only the prefix form. This form increments (or decrements) its operand and yields the changed object as its result. The postfix operators increment (or decrement) the operand but yield a copy of the original, unchanged value as its result:
int i = 0, j;
j = ++i; // j = 1, i = 1: prefix yields the incremented value
j = i++; // j = 1, i = 2: postfix yields the unincremented value
These operators require lvalue operands. The prefix operators return the object itself as an lvalue. The postfix operators return a copy of the object’s original value as an rvalue.
INFO
Advice: Use Postfix Operators only When Necessary
Readers from a C background might be surprised that we use the prefix increment in the programs we’ve written. The reason is simple: The prefix version avoids unnecessary work. It increments the value and returns the incremented version. The postfix operator must store the original value so that it can return the unincremented value as its result. If we don’t need the unincremented value, there’s no need for the extra work done by the postfix operator.
For int
s and pointers, the compiler can optimize away this extra work. For more complicated iterator types, this extra work potentially might be more costly. By habitually using the prefix versions, we do not have to worry about whether the performance difference matters. Moreover—and perhaps more importantly—we can express the intent of our programs more directly.
Combining Dereference and Increment in a Single Expression
TrickyThe postfix versions of ++
and --
are used when we want to use the current value of a variable and increment it in a single compound expression.
As one example, we can use postfix increment to write a loop to print the values in a vector
up to but not including the first negative value:
auto pbeg = v.begin();
// print elements up to the first negative value
while (pbeg != v.end() && *beg >= 0)
cout << *pbeg++ << endl; // print the current value and advance pbeg
The expression *pbeg++
is usually confusing to programmers new to both C++ and C. However, because this usage pattern is so common, C++ programmers must understand such expressions.
The precedence of postfix increment is higher than that of the dereference operator, so *pbeg++
is equivalent to *(pbeg++)
. The subexpression pbeg++
increments pbeg
and yields a copy of the previous value of pbeg
as its result. Accordingly, the operand of *
is the unincremented value of pbeg
. Thus, the statement prints the element to which pbeg
originally pointed and increments pbeg
.
This usage relies on the fact that postfix increment returns a copy of its original, unincremented operand. If it returned the incremented value, we’d dereference the incremented value, with disastrous results. We’d skip the first element. Worse, if the sequence had no negative values, we would attempt to dereference one too many elements.
INFO
Advice: Brevity Can Be a Virtue
Expressions such as *pbeg++
can be bewildering—at first. However, it is a useful and widely used idiom. Once the notation is familiar, writing
cout << *iter++ << endl;
is easier and less error-prone than the more verbose equivalent
cout << *iter << endl;
++iter;
It is worthwhile to study examples of such code until their meanings are immediately clear. Most C++ programs use succinct expressions rather than more verbose equivalents. Therefore, C++ programmers must be comfortable with such usages. Moreover, once these expressions are familiar, you will find them less error-prone.
Remember That Operands Can Be Evaluated in Any Order
Most operators give no guarantee as to the order in which operands will be evaluated (§ 4.1.3, p. 137). This lack of guaranteed order often doesn’t matter. The cases where it does matter are when one subexpression changes the value of an operand that is used in another subexpression. Because the increment and decrement operators change their operands, it is easy to misuse these operators in compound expressions.
To illustrate the problem, we’ll rewrite the loop from § 3.4.1 (p. 108) that capitalizes the first word in the input. That example used a for
loop:
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
*it = toupper(*it); // capitalize the current character
which allowed us to separate the statement that dereferenced beg
from the one that incremented it. Replacing the for
with a seemingly equivalent while
// the behavior of the following loop is undefined!
while (beg != s.end() && !isspace(*beg))
*beg = toupper(*beg++); // error: this assignment is undefined
results in undefined behavior. The problem is that in the revised version, both the left- and right-hand operands to =
use beg
and the right-hand operand changes beg
. The assignment is therefore undefined. The compiler might evaluate this expression as either
*beg = toupper(*beg); // execution if left-hand side is evaluated first
*(beg + 1) = toupper(*beg); // execution if right-hand side is evaluated first
or it might evaluate it in yet some other way.
INFO
Exercises Section 4.5
Exercise 4.17: Explain the difference between prefix and postfix increment.
Exercise 4.18: What would happen if the while
loop on page 148 that prints the elements from a vector
used the prefix increment operator?
Exercise 4.19: Given that ptr
points to an int
, that vec
is a vector<int>
, and that ival
is an int
, explain the behavior of each of these expressions. Which, if any, are likely to be incorrect? Why? How might each be corrected?
(a)ptr != 0 && *ptr++
(b)ival++ && ival
(c)vec[ival++] <= vec[ival]