17
$\begingroup$

Many languages have compound assignment statements, for example +=. However, for the common task of adding or subtracting 1 from a variable some languages (mostly those close to C) have additional ++ and -- operators. For example, x += 1 can be written as x++ in these languages. Some languages decide not to have these operators. This can make some sense, as there is already a short way to increment something. Why would you add an increment/decrement operator when you have compound assignments? For completeness, I'll allow arguments for the other position as well.

$\endgroup$
5
  • 4
    $\begingroup$ I think the reason is that the specialized corresponding add/subtract by 1 instructions exist in Assembly. $\endgroup$
    – CPlus
    Jul 8, 2023 at 0:42
  • 8
    $\begingroup$ FYI, the increment/decrement operators came first. C inherited them from B, which didn't have compound assignments. The latter were a generalization that was added in C. $\endgroup$
    – Barmar
    Jul 8, 2023 at 14:14
  • 4
    $\begingroup$ For one thing, "C+=1" would be an awful name for a programming language. $\endgroup$
    – bta
    Jul 10, 2023 at 19:43
  • 1
    $\begingroup$ Why add compound assignment operators when separate assignment and arithmetic operators exist? Some languages choose not to have compound assignment operators. This can make some sense, as there is already a way to increment / decrement / etc. something. Why would you add compound assignments when you already have ordinary assignment and ordinary arithmetic operators? $\endgroup$ Jul 10, 2023 at 20:40
  • 2
    $\begingroup$ In PDP-11 assembly (other assembly languages were likely similar), ++ and += were natural representations for INC and ADD. Whereas in assembler you'd write MOV X, Y; ADD X, Z (x = y; x += z; in C), C allowed you to write x = y + z. The fact that x += 1 and x++ are equivalent in the semantics of C doesn't change that fact that INC X was cheaper in several ways than ADD X, 1. $\endgroup$
    – chepner
    Jul 10, 2023 at 21:05

9 Answers 9

18
$\begingroup$

While x += 1 may be replaced by ++x, x++ is special in that it is essentially temp = x, x += 1, temp. I am making a C-like which is an evolution of C, so removing x++ was never even something I considered, but I will also say that I prefer to have ++x and x += 1 because they communicate different things in the code if both are available in the language.

++x tells me: "increment to the next value". That is, we're iterating over something. This might be walking through an array or looking at every value in a range. But it is not making any jumps or anything.

x += 1 on the other hand is saying "increase x by one", so this could be to adjust something by one, or jumping some amount, which just happened to be 1 but could just as well have been 2 or 5. Typically I would assume this means we're not using x to iterate (when ++ and -- exists in the language), and x += 1 is that hint.

That said, they are conveniences and can be dropped. Going as far as saying they're "actively harmful" like Swift does, seems like a very extreme opinion.

$\endgroup$
9
  • 10
    $\begingroup$ Yes, I've always been bothered by the use of x++ when ++x is appropriate. I know modern compilers will generate the same code, but my ancient brain still sees it as (temp = x, x += 1, temp). $\endgroup$ Jul 8, 2023 at 0:57
  • 2
    $\begingroup$ @RayButterworth Hey, it sounds better like that ngl (my brain is probably not as ancient). $\endgroup$ Jul 8, 2023 at 6:31
  • 2
    $\begingroup$ @Ray Modern compilers cannot generate the same code for each. For the actual increment or decrement, maybe. But the difference is that the ++a uses the new value immediately, wheras a++ continues to use the old value. The distinction is only resolved at the next "sequence point" (typically end of statement), which opens the door to a hatful of UB. $\endgroup$ Jul 8, 2023 at 7:33
  • 3
    $\begingroup$ If you look at it in SSA form the code will be pretty much identical, showing how the old value of x can easily be kept in a register. If you have int b = a++, then it's also possible to think of that as int b = a; a += 1;. The lack of sequence points is just by definition in C. A language can easily decide UB due to ++ and -- well defined. $\endgroup$
    – Nuoji
    Jul 9, 2023 at 8:24
  • 3
    $\begingroup$ @Paul_Pedant they can generate the same code in cases where the value isn't observed until after that sequence-point, which is true in the (extremely) common case where x++ is a statement on its own. $\endgroup$
    – hobbs
    Jul 10, 2023 at 1:43
14
$\begingroup$

C isn't a language that comes with fancy iteration constructs like foreach or map. This means programmers need to increment something every time they loop over indices of an array or a string, or over pointers.

Making i++ a shorthand for i += 1 makes a little more sense when you're going to spend so much time writing for (int i = 0; i < n; i++) or for (p = s; *p; p++) all over again.

Maybe more interestingly, even though += is more general and more powerful than ++, more power might be bad. Having increment be distinct from compound assignment means you are encouraged to think at a different level of abstraction.

You're not "using the full power of addition", you're just doing the loop thing.

You don't need to visually slow down to verify that you're actually adding the digit 1 instead of a lowercase l that crept in here, you're just doing the loop thing.

To make this point clearer, I could make a parallel with while being a distinct, less powerful version of goto (remember: more expressive power isn't necessarily a good thing). Reading a goto instills fear in me, and makes me read the entire function again because otherwise I might miss some subtle control flow possibility. Reading a while is just reading a regular, well-known and well-delimited local construct.

In a way, it is the same for ++. (And for equivalents in other languages, like inc in Pascal/Delphi.)

$\endgroup$
2
  • 5
    $\begingroup$ I would disagree. I've had to write loops with i += 2, or 4, or 8 rather than 1 enough times that it doesn't bother me when I see a for loop written that way. Heck, I've even sometimes seen those desugared into while loops, and the overall result is just as readable. $\endgroup$
    – Bbrk24
    Jul 7, 2023 at 21:52
  • 2
    $\begingroup$ I don't think loops with += are less readable at all. I'm saying it's harder to confuse a ++ loop with a += loop. $\endgroup$ Jul 8, 2023 at 7:22
12
$\begingroup$

The ++ and -- operators aren't really necessary. There are languages that get along just fine with x += 1 and x -= 1. Like Python, or Swift (which had these operators but removed them for being error-prone).

But because incrementing or decrementing a variable by 1 is extremely common (especially in a for loop), and because PDP-11 assembly language happened to make it easy to implement those operations, C's designers decided to provide ++ and -- as syntactic sugar. And then a bunch of other languages decided to adopt the C syntax for their operators.

$\endgroup$
9
  • 2
    $\begingroup$ This strikes me as a huge facepalm 🤦‍♂️ for swift. Their reasoning seems unsound given that they acknlowedge both the difference between statements and expressions (one returns a value) and the difference between pre and post increment. Perhaps there is some context in how people tend to write swift code. $\endgroup$ Jul 8, 2023 at 8:22
  • 3
    $\begingroup$ I couldn't disagree more with Swift's decision, but +1 for this as an answer to the question asked. $\endgroup$ Jul 8, 2023 at 10:13
  • 3
    $\begingroup$ @BruceAdams: Yeah, I'm not experienced with Swift, but the decision seems weird. They could have just changed the return value of the operators to Void, which would still allow x++ and x-- as standalone statements, while avoiding confusing uses like f(++x, x++). $\endgroup$
    – dan04
    Jul 8, 2023 at 19:21
  • 4
    $\begingroup$ @dan04 What would be the point of having x++ and ++x if they wouldn't return anything? The only difference between them is what they return. $\endgroup$
    – xigoi
    Jul 8, 2023 at 19:51
  • 2
    $\begingroup$ @xigoi: Just backwards compatibility. Both x++ and ++x would become redundant ways of spelling x += 1 as a standalone statement. $\endgroup$
    – dan04
    Jul 8, 2023 at 19:57
11
$\begingroup$

(Yes, I'm writing another answer to this question.) Sometimes i++ really is what you need. Here's an example where it shines. Consider the following code fragment:

int index = 0;
Set(index++, "Draw");
Set(index++, GetDistance());
Set(index++, GetColor());
Set(index++, GetOptions());

How would you write that in a language that doesn't have, specifically, the postfix increment operator?


You could use constants:

Set(0, "Draw");
Set(1, GetDistance());
Set(2, GetColor());
Set(3, GetOptions());

But this is error prone! If you ever need to reorder these lines, or to insert another line, or to delete one of those lines, it's easy to forget updating the index constants.


You could use compound assignment:

int index = 0;
Set(index, "Draw");
index += 1;
Set(index, GetDistance());
index += 1;
Set(index, GetColor());
index += 1;
Set(index, GetOptions());
index += 1;

But this is twice as many lines of code! And the alternation of lines isn't as easy to inspect... We make this look better:

int index = 0;
Set(index, "Draw"); index += 1;
Set(index, GetDistance()); index += 1;
Set(index, GetColor()); index += 1;
Set(index, GetOptions()); index += 1;

But now the code is unaligned, again it is not easy to verify that the repeated part is correctly done and no index += 1 was forgotten (imagine longer lines). We can do better by moving the similar part towards the beginning of the line:

int index = -1;
index += 1; Set(index, "Draw");
index += 1; Set(index, GetDistance());
index += 1; Set(index, GetColor());
index += 1; Set(index, GetOptions());

Now the code is compact and easy to verify again... But the index starts at -1, that's weird!


You could introduce a separate function:

int ReturnAndInc(int &i) { *i += 1; return *i; }
[...]
int index = 0;
Set(ReturnAndInc(&index), "Draw");
Set(ReturnAndInc(&index), GetDistance());
Set(ReturnAndInc(&index), GetColor());
Set(ReturnAndInc(&index), GetOptions());

Now this is good! Code is compact, it's easy to insert/delete/update lines, it's easy to verify that the index is good!

But we basically reimplemented i++.


Looks like sometimes ++ is useful after all. Now, this doesn't mean it needs to be built-into the language! The last solution can be acceptable, even more so if it can inline ReturnAndInc.

$\endgroup$
9
  • 2
    $\begingroup$ You could also have the helper function do both the increment and the call to Set... But what if Set has overloads? Now you need to make many similar helper functions, kept in sync with the number of overloads... I didn't want to include that case in the answer. $\endgroup$ Jul 8, 2023 at 8:08
  • 1
    $\begingroup$ Yuck. Modern language design includes elements of adding syntactic sugar to remove boilerplate. Removing it for something so common seems wrong. $\endgroup$ Jul 8, 2023 at 8:24
  • 3
    $\begingroup$ You could use i = -1; Set(i += 1, "Draw"); Set(i += 1, GetDistance()); etc. too, but that's also a bit awkward and then not everyone likes using (compound) assignments as expressions. Then again, why doesn't your Set() thingy have an append-like function that would keep track of the last used / first free index? $\endgroup$
    – ilkkachu
    Jul 8, 2023 at 14:55
  • 1
    $\begingroup$ Swift, in which ++ was removed, also decided to have += return void... And making a Set() that also increments the index is boilerplate work that might not always pay for itself, compared to sprinkling some ++. $\endgroup$ Jul 8, 2023 at 19:17
  • 2
    $\begingroup$ Honestly I think the version that uses the constants is the most clear one. If you have a lot of these you can just iterate an integer range instead of mutating a variable. $\endgroup$
    – Aiono
    Jul 8, 2023 at 19:54
7
$\begingroup$

In many cases, ++ and -- are used to move to the "next" thing or the "previous" thing. Although this is equivalent to adding 1, the essential concept embodied in the operation is "next" or "previous", and not "1".(*)

The situation is somewhat analogous to the way many languages allow a programmer to write if (flags & BITMASK) ... rather than if ((flags & BITMASK) != 0). One might describe the former operation as "is the set of bits in common between the two values non-empty" and the latter as "is the set of bits of values equal to something other than the empty set".

IMHO, good languages should avoid requiring programmers to write "phony constants". If there's only one constant value that has a certain property, having a construct that embodies that property is cleaner than requiring that programmers specify the explicit construct.

(*) This becomes especially true if one views bytes of memory as being located between addresses, but with the convention that a single address will by default refer to the immediately following storage. Under this view, a 16-byte region of memory will have 17 addresses, with all but the last each uniquely pointing to the start of a byte, and all but the first each uniquely point to the end of a byte.

Viewing memory in this fashion, "incrementing" a pointer means that it should be made to identify an object that starts where the current object ends, and "decrementing" a pointer means it should be made to identify an object that ends where the current object starts. Note that in this scenario, there would be no space between the previously and newly identified objects.

$\endgroup$
5
$\begingroup$

Why add an increment/decrement operator when compound assignnments exist?

Why add compound assignment when regular assignment exists? After all, in C, x += 1 is equivalent to x = x + 1. Going further, there are many things that look redundant: why have all three of do..while, while and for? Why have loops to begin with if you can use goto or recursion instead? Why do CPUs have all these instructions when x86's mov instruction is Turing complete?

Languages are created to make programming more easy, but you also have to remember that you need to be able to create an interpreter and/or compiler for it. It's easy to forget that when K&R released their first specification of C, most consumer computers were still using 8-bit processors, and 64 kiB of RAM was considered a large amount. The PDP-11 that C was developed on was a 16-bit computer which could be made to support up to 4 MiB of RAM, but programs were still limited to a 16-bit address space. So compilers couldn't be very fancy.

As dan04 already mentioned, the PDP-11 had pre- and post-increment operations in its instruction set. So having increment/decrement operators in the language that are easy to parse and easy to generate instructions for makes a lot of sense. Also, consider that incrementing by one is an extremely common operation (already mentioned in several other answers), so it makes programming in C more easy. Consider this string copy function, which is just a one-liner when using post-increment on the pointers:

void strcpy(char *dst, const char *src) {
    while((*dst++ = *src++));
}

Some people object to this because they find it hard to follow. What if I write:

x = f(x++, x++);

Even if it's well-defined it's hard to tell what this does without knowing exactly what the rules are for the ordering of operations here. It's worse if it's implementation-defined what will happen. That's an argument against these operators.

$\endgroup$
2
  • $\begingroup$ x += 1 is not necessarily equivalent to x = x + 1 if you substitute a more complex expression for x, such as arr[rand() % arr_len]. $\endgroup$
    – xigoi
    Jul 8, 2023 at 19:55
  • 1
    $\begingroup$ @xigoi True, but you can easily work around that without needing compound assignment. My point is that a lot of things in languages are not really necessary, even if they are very convenient. $\endgroup$
    – G. Sliepen
    Jul 8, 2023 at 21:10
4
$\begingroup$

C had those operators because C was designed to mirror the assembly of the particular machine it was developed for. Some modern languages have no such operator -- Swift in particular cited that ++ and -- were "actively harmful".

$\endgroup$
1
  • 5
    $\begingroup$ It wasn't exactly designed for the purpose of mirroring the machine language; it was designed to take advantage of it. Compiling ++x trivially generated a single instruction. Compiling x+=1 would require testing for the 1 value in order to decide to handle that special case, which would make the compile take longer. (And yes, compilers really were very slow, so any time saving was worth it.) $\endgroup$ Jul 8, 2023 at 0:54
2
$\begingroup$

There are some good long answers. The short one is that it makes working with arrays and other containers with dense index-based access easier, shorter and less error prone. You can easily misstype 2 instead of 1.

$\endgroup$
2
$\begingroup$

There are some important semantic differences between increment and decrement operators vs the addition and subtraction assignment operators.

Consider an iterator walking some kind of data structure like an array vs an expensive generator function. It may not be possible to jump to the Nth element without computing the intermediate elements first.

So while:

iter += 3

is equivalent to:

iter.next().next().next()

and indeed you can construct the whole of arithmetic this way, it is not necessarily efficient.

Defining a additional assignment operator can be used to imply that there is a short cut.

In some languages it is useful to imply the computational cost but not defining additional assignment if it is not efficient.

So for a number type you can easily do:

 x = 1;
 x += 5;  // what it looks like - just a simple addition operator

It would seem less intuitive to write:

 class iterator
 {
     calculate_next();
     opeator++() { calculate_next(); }
 };

 foo++   // fine as alias for calculate_next;
 foo+=5  // less obvious as an alias for calculate_next repeated 5 times;

There is potentially a loop in the second case which does not exist in the first case. Though it gives the right answer, Peano arithmetic is not computationally efficient.

Another difference is that increment and decrement tend to be usable as expressions while addition assignment tends to useable only as a statement.

 x+=2

is more intuitive than:

 y=(x+=2)

If used as an expression it would also be a pre-increment whereas x++ is a post increment.

With increment and decrement operators (in C and C++ at least) you can define whether the value is incremented before or after the return.

That is in the context of:

 y=++x;

 ++x    

means:

 x=x+1;
 return x;

Whereas in the context of:

 z=x++;

 x++

means:

 ret=x;
 x=x+1;
 return ret;

Thus:

 y > z

All this is true even without considering the historical origins of increment and decrement in relation to C as a layer on top of assembly languages which may themselves have distinct increment and addition operations.

$\endgroup$
1
  • 1
    $\begingroup$ This is the most compelling reason, IMO. $\endgroup$ Jul 10, 2023 at 9:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .