95

So I've been reading up on lvalue and rvalues and I'm a bit confused about the difference between ++x and x++ when it comes to this categorization.

Why is ++x a lvalue and x++ a rvalue?

samuelnj
  • 1,627
  • 1
  • 10
  • 19
  • 1
    What would happen if you did `x++ = 5`? Would it set `x` to 5 and then increment so `x` would be 6? Or would it increment and then do the assignment so `x` would be 5? When would you want `x++` to be an l-value? – scohe001 Jun 11 '18 at 17:09
  • 39
    In short: `++x` returns a reference to the object you incremented, where as `x++` returns a temporary copy of `x`'s old value. – Carl Jun 11 '18 at 17:09
  • 7
    `++x` returns `x`. `x++` returns some nameless thing (an rvalue) with `x`'s type and it's old value. – François Andrieux Jun 11 '18 at 17:10
  • 2
    @Fureeish, I can see that the questions are similar, but I think the question you're referencing is somewhat confusing to read... and slightly different. – samuelnj Jun 11 '18 at 17:34
  • It may help to recall that an lvalue has a name, and persists beyond the current expression. – Drew Dormann Jun 11 '18 at 17:43
  • 13
    “Your standards committee members were so preoccupied with whether or not they could, they didn’t stop to think if they should.” – David Conrad Jun 11 '18 at 22:05
  • 1
    @DrewDormann not necessarily, e.g. `*("a"s + "b"s).c_str()` is an lvalue. – M.M Jun 11 '18 at 23:34
  • 3
    @scohe001 so what would happen if you did `++x = 5`? Would it increment and then do the assignment so x would be 5? What's the point of the increment anyway? – phuclv Jun 12 '18 at 04:58
  • @DavidConrad the horror! – Anthony Jun 12 '18 at 15:36

6 Answers6

112

++x returns a reference to the object you incremented, where as x++ returns a temporary copy of x's old value.

At least this would be the "normal" way by to implement these operators by convention. And all built-in types work this way. And if you've read about lvalues / rvalues then you would see that since the prefix operator returns the named object itself it would be an lvalue, where as the postfix operator returns a copy of a local temporary, which would then qualify as an rvalue.

Note: Also, we have prvalues, xvalues and such now, so it's technically a bit more complicated these days. Look here for more info.

Carl
  • 2,057
  • 1
  • 15
  • 20
  • 8
    Doesn't this answer basically just repeats/rewords the original question? – AnT stands with Russia Jun 12 '18 at 01:04
  • "Why do 1 and 2 sum to 3?" -> "Because if you add 1 and 2, you get 3." I.e., while surely you add some detail to this topic, I don't see an actual answer here. – Sebastian Mach Jun 12 '18 at 07:37
  • 8
    Adding some detail is all you ever do in "why" questions. As long as this detail is enough to bring the topic close enough to what the person asking the question is familiar with, that's good enough for that person. See https://www.lesswrong.com/posts/W9rJv26sxs4g2B9bL/transcript-richard-feynman-on-why-questions – etarion Jun 12 '18 at 08:30
  • 2
    @AnT If we look at statements like a = ++b; c = b++; then the rationale behind the response from Carl will be clear. The pre-increment operation's value is assigned to an lvalue variable ('a'), whereas it is only b that is assigned to c, but its post-incremented value is just let off the air (it is temporary). So, pre-increment is lvalue and post-increment is rvalue. – Seshadri R Jun 12 '18 at 12:18
  • 1
    @SeshadriR As you point out, the first sentence is the core essence of the answer, and the rest is some extra information to help OP wrap their head around it a little more. And the link to extra reference if he want to read more about the topic. – Carl Jun 12 '18 at 13:35
  • It could have been declared the other way around. This answer does not explain the rationale behind this design decision. It just says, "*because that is the (usual) definition*". That's why it's controversial. – luk32 Jun 13 '18 at 12:51
46

C++ (as opposed to C) is a devoted lvalue-preserving language: it strives to painstakingly preserve the "lvalueness" of an expression whenever it is possible.

  • It is very easy to preserve the "lvalueness" of pre-increment: just increment the operand and return it as an lvalue. Done. The returned lvalue will contain exactly the result it is supposed to contain: the new (incremented) value of the operand.

  • And at the same time it is virtually impossible to preserve "lvalueness" of post-increment: by definition, the result of post-increment is the old (original) value of the operand. If you attempt to return an lvalue from post-increment, you will have to somehow simultaneously ensure two things: 1) the lvalue is incremented, 2) the calling code sees the old value when it looks into that same lvalue (!). This combination of requirements is so contradictory that is basically impossible to implement in C++ object model.

    In order to implement the proper post-increment behavior one has to make sure that the calling code does not look directly into the operand, but rather looks into some conceptual or physical "proxy" that makes the calling code to "see" the old value of the operand. That proxy might be a temporary object that holds the old value. Or that proxy might be something that generates the old value on the fly by subtracting 1 from the new value. In any case, that proxy is what prevents the calling code from accessing the original lvalue.

This is why C++ takes advantage of the easily achievable opportunity to preserve "lvalueness" of pre-increment, but concedes to the impossibility to achieve the same with post-increment. In case of post-increment it is just not worth the effort to deviate from classic standard C behavior, which tends to discard "lvalueness" quickly and happily.

Community
  • 1
  • 1
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • lvalueness. The essence of lvalue. Awesome. – Anthony Jun 12 '18 at 15:39
  • The result of post-increment can't very well be a "real" lvalue, but it could have been a quasi-lvalue somewhat to a bitfield, which doesn't have an address but does support assignment. I don't think adding a quasi-lvalue concept just for post-increment would be helpful, but it may sometimes be useful to have types support such a construct with distinct operations for simple read, simple write, and operations for each phase of a read-modify-write sequence (allowing for the possibility that compound assignment operators could e.g. acquire a lock on the read and release it on the write). – supercat Jun 12 '18 at 17:19
  • If such quasi-lvalues existed, then a postfix ++ operator could yield an object of a compiler internal type whose simple read would do a read and increment, and whose write operation would increment the value before writing. I don't see postfix inc/dec as being sufficient in and of themselves to justify quasi-lvalues, but such things could be useful for a variety of other reasons (as with the above scenario with locking). – supercat Jun 12 '18 at 17:23
30

Maybe writing down the workings in pseudo-code of the operators for int makes it more clear:

prefix:

int& Prefix(int& val)
{
  int& value = val;
  value += 1;
  return value;
}

postfix:

int Postfix(int& val)
{
  int oldValue = val;
  val += 1;
  return oldValue; 
}

The difference is in what is returned by the two operators, one by value and one by reference.

anatolyg
  • 26,506
  • 9
  • 60
  • 134
Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
  • I suppose you should include the `int` parameter of the postfix operator. – Carl Jun 11 '18 at 17:16
  • @Carl I could, but as I said this is psuedo-code and I wanted to make clear in the code snippets and to the op that `val` are the same values here, both being the value of the int that the operator works on. However, you're right, in legal C++ you'd have to. – Hatted Rooster Jun 11 '18 at 17:16
  • Makes sense, didn't see the pseudo-code part of your answer, went straight to the code. – Carl Jun 11 '18 at 17:19
  • 1
    The int parameter of the postfix operator isn't "real", it's just there as a crude way to disambiguate the signatures of `operator++`. The language designers could have just as easily decided to call it `++operator()` and `operator++()` rather than `operator++()` and `operator++(int)` but probably decided not to because it would have made language parsing even more complicated without really being any less confusing. – Miral Jun 12 '18 at 00:35
14

Why is ++x a lvalue

I am guessing it is an lvalue because it can be.

++x it can be thought of as

((x = x + 1), x)  // Increment x. Then evaluate to the value of x

or

((x += 1), x)    //  Increment x. Then evaluate to the value of x

It makes sense to evaluate ++x to an lvalue.

x++ a rvalue?

Because it can't be an lvalue.

x++ it can be thought of as

((unnamed_variable = x), (x = x + 1), unnamed_variable)

or

((unnamed_variable = x), (x += 1), unnamed_variable)

x++ evaluates to the value of before x is incremented. Since there is no variable that can store that value, it cannot be an lvalue.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • `++x` is defined as `x += 1` . Not sure what you are trying to illustrate by adding `, x` – M.M Jun 11 '18 at 23:32
  • 3
    @M.M, that the expression evaluates to `x`. – R Sahu Jun 12 '18 at 03:37
  • What @MM wanted to say is that x += 1 also evaluates to x. Yes, it's completely unnecessary to specify ,x. – Veky Jun 12 '18 at 06:51
  • 4
    @Veky, as does `++x`. I wanted to simplify it as much as I thought was needed. – R Sahu Jun 12 '18 at 12:56
  • 4
    @Veky, Since not everyone (likely including people like the OP) knows what `x += 1` evaluates to, making it explicit helps explain how things work. – LarsH Jun 12 '18 at 16:09
  • It's fascinating that there are people who consider a,b simpler than just a. It's also fascinating that some believe that comma operator is somehow better understood than += (in terms of return value). – Veky Jun 14 '18 at 06:20
11

For built-in types, such as int, x++ yields the old value of x. There is no storage associated with this, so it would be impossible for the expression to be an lvalue.

In C, ++x yielded the new value of x (and was not an lvalue). Since the object x actually contains that same value, it is possible to have ++x designate the object x (i.e. be an lvalue). This is added functionality compared to yielding an rvalue, and the designer of C++ decided that this would be an improvement to the language.

For class types, it is possible overload any operator in such a way that use of the operator can yield an lvalue, xvalue, or prvalue. However it is considered good style to make overloaded operators have similar semantics to built-in operators, which is why it is normal for people to overload ++x to yield an lvalue and to overload x++ to yield an rvalue.

M.M
  • 138,810
  • 21
  • 208
  • 365
9

First of all, an "lvalue" is an expression that is legal on the left side (the "l" in "lvalue") of an assignment. That means it represents an address whose contents can be changed by the assignment. (The C standard calls such a thing an "object".) That's why, e.g., infix operator expressions and function calls aren't lvalues.

"rvalue" is a silly term; any well-formed expression is legal on the right side of an assignment (setting aside type conversion issues).

The standard says that, e.g.:

i = ++i + 1;
a[i++] = i;

..are both "undefined statement expressions", meaning their implementation (and therefore behavior) is not standardized.

The common sense meaning of "++i" is "increment the value of the object i, and evaluate to the result". Similarly, "i++" means "evaluate to the current value of the object i, and increment the object's value afterward". Neither says what happens when "++i" or "i++" is used as an lvalue.

The standard says, about expressions: "The value computations of the operands of an operator are sequenced before the value computation of the result of the operator." This is actually ambiguous for "++i", since the value computation of the operand is affected by the value computation of the operator.

What makes the most sense to me is this:

i = 5;
++i = i + 1;   /*i is set to 6*/
i++ = 1;       /*i is set to 1*/
++i = ++i + 1; /*i is set to 8, but problematic*/
i++ = i++ + 1; /*i is set to 8, but problematic*/
++i = i++ + 1; /*i is set to 8, but problematic*/

My advice -- don't use expressions that are so hard to understand!

  • 1
    lvalue vs rvalue is actually very important in C++ 11 and beyond for things like `std::move()` , `std::forwad()` and rvalue references. Although, admittedly its not worth calling `std::move()` on a single int... – samuelnj Jun 12 '18 at 21:21