Why Is ++I Considered an L-Value, But I++ Is Not

Why is ++i considered an l-value, but i++ is not?

Well as another answerer pointed out already the reason why ++i is an lvalue is to pass it to a reference.

int v = 0;
int const & rcv = ++v; // would work if ++v is an rvalue too
int & rv = ++v; // would not work if ++v is an rvalue

The reason for the second rule is to allow to initialize a reference using a literal, when the reference is a reference to const:

void taking_refc(int const& v);
taking_refc(10); // valid, 10 is an rvalue though!

Why do we introduce an rvalue at all you may ask. Well, these terms come up when building the language rules for these two situations:

  • We want to have a locator value. That will represent a location which contains a value that can be read.
  • We want to represent the value of an expression.

The above two points are taken from the C99 Standard which includes this nice footnote quite helpful:

[ The name ‘‘lvalue’’ comes originally
from the assignment expression E1 =
E2, in which the left operand E1 is
required to be a (modifiable) lvalue.
It is perhaps better considered as
representing an object ‘‘locator
value’’. What is sometimes called
‘‘rvalue’’ is in this International
Standard described as the ‘‘value of
an expression’’. ]

The locator value is called lvalue, while the value resulting from evaluating that location is called rvalue. That's right according also to the C++ Standard (talking about the lvalue-to-rvalue conversion):

4.1/2: The value contained in the object
indicated by the lvalue is the rvalue
result.

Conclusion

Using the above semantics, it is clear now why i++ is no lvalue but an rvalue. Because the expression returned is not located in i anymore (it's incremented!), it is just the value that can be of interest. Modifying that value returned by i++ would make not sense, because we don't have a location from which we could read that value again. And so the Standard says it is an rvalue, and it thus can only bind to a reference-to-const.

However, in constrast, the expression returned by ++i is the location (lvalue) of i. Provoking an lvalue-to-rvalue conversion, like in int a = ++i; will read the value out of it. Alternatively, we can make a reference point to it, and read out the value later: int &a = ++i;.

Note also the other occasions where rvalues are generated. For example, all temporaries are rvalues, the result of binary/unary + and minus and all return value expressions that are not references. All those expressions are not located in an named object, but carry rather values only. Those values can of course be backed up by objects that are not constant.

The next C++ Version will include so-called rvalue references that, even though they point to nonconst, can bind to an rvalue. The rationale is to be able to "steal" away resources from those anonymous objects, and avoid copies doing that. Assuming a class-type that has overloaded prefix ++ (returning Object&) and postfix ++ (returning Object), the following would cause a copy first, and for the second case it will steal the resources from the rvalue:

Object o1(++a); // lvalue => can't steal. It will deep copy.
Object o2(a++); // rvalue => steal resources (like just swapping pointers)

Why can't a++ (post-increment operator) be an Lvalue?

And As per rule of post increment the value of variable a will increment only after execution of that statement.

That's a bit misleading. The variable is incremented immediately. But the result of the expression is the old value. This should make it easier to understand why it cannot be an lvalue. The modified object doesn't have the old value, so the hypothetical lvalue cannot refer to that object. The value is a new, temporary object; it is a prvalue.

As an analogy, think about writing a function that does the same thing as post increment. This is how it would be written (if you define overloaded operator overload for a class, then a function such as this is quite literally what you'd write with a few changes):

int post_increment(int& operand)
{
int old = operand;
operand += 1;
return old;
}

How could you meaningfully re-write that function to return an lvalue (reference) and still have the behaviour expected from the post increment?

Why ++variable isn't treated as lvalue in C?

The prefix increment operator ++ results in the incremented value of its operand, however it is not an lvalue. Section 6.5.3.1p2 of the C standard: describes the semantics as follows:

The value of the operand of the prefix ++ operator is incremented.
The result is the new value of the operand after incrementation. The
expression ++E is equivalent to (E+=1). See the discussions of
additive operators and compound assignment for information on
constraints, types, side effects, and conversions and the effects of
operations on pointers.

Then section 6.5.16.2p3 regarding compound assignment operators states:

A compound assignment of the form E1 op = E2 is equivalent to the
simple assignment expression E1 = E1 op (E2), except that the lvalue
E1 is evaluated only once, and with respect to an
indeterminately-sequenced function call, the operation of a compound
assignment is a single evaluation.

And 6.5.16p3 regarding the assignment operator further states:

An assignment operator stores a value in the object designated by the
left operand. An assignment expression has the value of the left
operand after the assignment, but is not an lvalue.

So it is explicitly not allowed. Even if it was, an expression such as ++a += 1 would cause a to be modified more than once without an intervening sequence point which would trigger undefined behavior.

This is one of those place where C and C++ differ. C++ does in fact allow the result of the = operator, and by extension compound assingment and prefix ++/--, to be an lvalue.

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

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

why (var) is considered as lvalue?

The expression n is an lvalue ; and putting parentheses around an expression does not change the value category.

As covered in the question already linked:

  • decltype( (n) ) is the syntax decltype( expression )
  • decltype( n ) is the syntax decltype( id ) .

The latter is not decltype expression with an expression of (n), there is no such syntax.

Why pre-increment operator gives rvalue in C?

C doesn't have references. In C++ ++i returns a reference to i (lvalue) whereas in C it returns a copy(incremented).

C99 6.5.3.1/2

The value of the operand of the prefix ++ operator is incremented. The result is the new value of the operand after incrementation. The expression ++Eis equivalent to (E+=1).

‘‘value of an expression’’ <=> rvalue

However for historical reasons I think "references not being part of C" could be a possible reason.

Why when r-value reference is being assigned to is considered as an l-value reference?

You write this:

even though the type of value is int&&, when it's being used as a value [and not an expression(as with decltype)], it suddenly becomes an l-value.

One high-level way to think about lvalues and rvalues is that lvalues have names and can be assigned to. value obviously has a name, so it shouldn't be surprising that it's an lvalue.

One way to think of std::move() or your static cast is that it not only casts its argument to the correct type, but it also produces an rvalue expression.

Thinking about your two functions s and f:

  • Presumably, your function s takes an rvalue reference because you want to operate on rvalues, e.g. steal their resources with a move operation.
  • In such a function, you might want to call functions on value that either (i) steal its resources, or (ii) treat it as an lvalue and do not steal its resources.
  • The language lets you call std::move() for case (i), to tell f that f may steal resources.
  • The language lets you pass value to functions without std::move(), as an lvalue, for case (ii), so you can call functions without worrying about that. This might be desirable if you want s to steal resources later on.

This question is pretty similar: Rvalue Reference is Treated as an Lvalue?



Related Topics



Leave a reply



Submit