It's a little more subtle than that. "val" is definitely an lvalue. However, when returning a local variable like this, C++11 has a rule: "if the NRVO activates, there are no copies/moves. Otherwise, see if you can treat val as an rvalue - if so, you get a move. That almost always works, but if it doesn't, fall back to treating it as an lvalue so it compiles with a copy like it used to."
This is the one time in the language that lvalues are automatically moved from, and it's because the language/compiler knows that returning from a function will destroy all local variables, so it's safe to move from them.
I see that makes sense. I wonder if at some point we should go back to the drawing board on whether the names "lvalue" and "rvalue" make sense. I keep on going back to the mathematical LHS and RHS semantics to figure it out but I can see why other programmers and myself get confused on these sorts of specifics.
Giving the compiler the ability to move implicitly surprises me since moves can have side-effects as you've said.
This is the one time in the language that lvalues are automatically moved from, and it's because the language/compiler knows that returning from a function will destroy all local variables, so it's safe to move from them.