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

C++C++11

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

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

Solution 2 - C++

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.

Solution 3 - C++

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.

Solution 4 - C++

>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.

Solution 5 - C++

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.

Solution 6 - C++

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!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsamuelnjView Question on Stackoverflow
Solution 1 - C++CarlView Answer on Stackoverflow
Solution 2 - C++AnTView Answer on Stackoverflow
Solution 3 - C++Hatted RoosterView Answer on Stackoverflow
Solution 4 - C++R SahuView Answer on Stackoverflow
Solution 5 - C++M.MView Answer on Stackoverflow
Solution 6 - C++Stephen F. HeffnerView Answer on Stackoverflow