Skip to content

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:

c++
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 ints 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

Tricky

The 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:

c++
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

c++
cout << *iter++ << endl;

is easier and less error-prone than the more verbose equivalent

c++
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:

c++
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

c++
// 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 begand the right-hand operand changes beg. The assignment is therefore undefined. The compiler might evaluate this expression as either

c++
*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]