s
is already an lvalue, like any other named variable. So having an lvalue reference to it doesn't really change anything.
The key conceptual difference between an rvalue and an lvalue is that you can only access an rvalue once, which makes it safe to do weird things to it. But if that one access is binding it to an rvalue reference, you've just created a way to access it multiple times, i.e. you turned it into an lvalue. So you can't implicitly bind it to another rvalue reference, whereas you can bind it to an lvalue reference.
Edit:
The two things are not logically equivalent. In one, you bind an rvalue to an lvalue reference. That's not allowed, because it's misleading. (Not quite as dangerous as allowing an lvalue to bind to an rvalue reference, but still misleading.) foo2
, by taking an lvalue reference, announces that it will probably modify the object passed and that this is absolutely a central part of the function's operation. Passing a temporary to such a function doesn't make sense, because you couldn't observe the modification.
In the other, you bind an rvalue to an rvalue reference. foo
, by taking an rvalue reference, announces that it might modify the object passed, but that this is a performance optimization and is not meant to be observed by anyone outside. Passing a temporary makes sense, but passing a non-temporary would be very dangerous, which is why you would have to explicitly std::move
the argument in such a case.
Then, in foo
, you bind the lvalue (which happens to be an expression referring to a variable of rvalue reference type, but that's irrelevant) to an lvalue expression. This makes sense: you've got your rvalue reference variable s
which points to something which, by virtue of having been passed to your function, has become your plaything. So if you want to pass it to something that modifies its state (such as foo2
) and then observe the changes (which you can, through s
or s2
both), that's conceptually perfectly sound.
Basically, being something that can be observed just once or multiple times is a matter of scope. And that's why the "same" thing (it's not really the same thing, but you think of it that way) can be bound to an rvalue reference in one context, and to an lvalue reference in another.