Why this two rvalue references examples have different behavior?

安稳与你 提交于 2019-12-05 00:16:50


First example

int a = 0;
auto && b = ++a;
cout << a << b << endl;

prints 22

Second example

int a = 0;
auto && b = a++;
cout << a << b << endl;

prints 20

Question: Why in first example ++a in 3rd line also increments b, and why there is no such behavior in second example?

Update: New question arised.


Because pre-increment (++a) first increments the value of a, stores the result, and then returns the reference to a. Now a and b effectively point to the same object.

Post-increment (a++), however, first stores the current value of a in a temporary, increments a, and returns this temporary - to which your rvalue ref points. a and b point to different objects, more specifically - b is a temporary holding the value of a prior to incrementing.

This is the reason why it's encouraged to use ++it over it++ for iterators and other complex objects that define increment / decrement: the latter creates a temporary copy and thus may be slower.


The difference is that ++a is an lvalue, however a++ is not. This is specified by C++14 [expr.pre.incr]/1:

The operand of prefix ++ is modified by adding 1 [...] The operand shall be a modifiable lvalue. [...] The result is the updated operand; it is an lvalue

and [expr.post.incr]/1:

[...] The result is a prvalue.

Now we consider auto && b = ++a; . ++a is an lvalue. auto&& is a forwarding reference. Forwarding references can actually bind to lvalues: the auto may itself deduce to a reference type. This code deduces to int &b = ++a;.

When a reference is bound to an lvalue of the same type, the reference binds directly, so b becomes another name for a.

In the second example, auto && b = a++;, a++ is a prvalue. This means it doesn't have an associated address and it's no longer any relation to the variable a. This line has the same behaviour as ++a; auto && b = (a + 0); would.

Firstly, since a++ is a prvalue, auto&& deduces to int&&. (i.e. auto deduces to int). When a reference of non-class type is bound to a prvalue, a temporary object is copy-initialized from the value. This object has its lifetime extended to match the reference.

So b in the second case is bound to a different object from a, a "temporary" int (which is not really so temporary, since it lasts as long as b does).

The reference binding rules are in [dcl.init.ref].


In the second case (post-increment) b actually references the temporary created for (a++), so the increments do not affect b.