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