[Edited] For anyone like me stuck on his language: the phrase "move from" should be understood as a technical term loosely related to the English language meaning of the words. I think the post would be better if he explained this terminology; as it is you have to know an awful lot about the topic he is writing about to even parse what he is saying.
There is a pretty good stack overflow post that quuxplusone linked below. How they explain it:
Moving from lvalues
Sometimes, we want to move from lvalues. That is, sometimes we want the compiler to treat an lvalue as if it were an rvalue, so it can invoke the move constructor, even though it could be potentially unsafe. For this purpose, C++11 offers a standard library function template called std::move inside the header <utility>. This name is a bit unfortunate, because std::move simply casts an lvalue to an rvalue; it does not move anything by itself. It merely enables moving. Maybe it should have been named std::cast_to_rvalue or std::enable_move, but we are stuck with the name by now.
Now, if you don't know what "move semantics" is, then "lvalues can't be moved from" isn't terribly helpful, and if you do then it's tautological, so I'm not saying you're wrong to criticize. :) But in a C++ context, "move" does have a single specific meaning — the one he's using properly if opaquely-to-non-C++ers.
But here's the gist: sometimes you have an object you want to copy, but then abandon the original. Maybe it's to return an object from a function. Maybe it's to insert the object into a larger structure. In these cases, copying can be expensive and it would be nice if you could just "raid" the original object to steal bits of it and construct the "copy" out of the raided bits. C++11 enabled this with rvalue references, std::move, and rvalue reference constructors.
This added a lot of "what the hell is this" to C++ code and a lot of new mental-model stuff to track for programmers. I understand why it was all added, but I have deep misgivings about the added complexity.
I find that this can reduce overall complexity. It makes it possible to use objects that can not be copied (such as a file descriptor wrapper) and moving can in most cases not fail. Without move semantics you'd have to use smart pointers to get similar results but with extra overhead.
Presumably, if you already (mistakenly) believe that a prvalue is a temporary, then you probably have at least a vague idea of C++ move semantics. If you don't already believe that then you are probably not the audience for the article.
Interesting stuff! I knew about lvalues and rvalues but I never knew about concepts like "glvalue" or "prvalue" or "xvalue" that the linked page talks about.
It makes sense that C++ avoids unnecessary copying or object creation whenever possible, that's pretty much C++'s M.O.
C++ is so complicated that I had to almost fail my exam and few years later I had to relearn C, get some experience in a real business project, and then I could start learning C++.
I find that understanding how memory is layed out in executable, how the C works in terms of stack, symbols etc is the introductory knowledge I had to obtain to even think about C++. Not sure what's there now, because I saw recently some great compiler warnings, but I'm pretty sure that I did convert a prvalue to a pointer reference (&) at least once in my life and later failed with memory problems, but no compiler errors
Getting failures later after coercing something to a reference is even easier than that; just deference a null pointer when passing to an argument that takes a reference; no warnings or errors! https://godbolt.org/z/xf5d9jKeh
When people say "Rust is just as complex as C++," I think value categories are a great example of why it's actually simpler, even if it also seems complex. C++ has three primary categories: prvalue, xvalue, and lvalue. There's also glvalue and rvalue. Rust has two: a place (which is a glvalue) and a value (which is a prvalue).
C++ needs those extra categories, they exist for good reasons. But it is more complex.
This is like saying 1 + 2 isn't addition because the compiler will optimize it away. It isn't an addition instruction in the emitted code but logically speaking it is addition.
Similarly just because a compiler may optimize a prvalue away doesn't change the fact that a prvalue by definition of the language is a temp.
To this day, I'm still having trouble remembering these names: lvalue, rvalue, prvalue, xvalue, glvalue.
While there was only lvalue, and rvalue - it was easy - LEFT and RIGHT - gives you the right intuition.
But now - if it has identity - "glvalue" - why not "ivalue" or "idvalue"?
And then can be moved - "rvalue" ? Why "rvalue", why not "mvalue", or "move-value"? Why the language have to be so constricted when comes to such an important bit - I mean we spell "value" fully, but miss the important bit...
Anyway just a rant... I'm having the same issue understanding "math" sometimes because of all the cryptic notations, and archaic symbols used.
I also enjoyed the linked article about the 5 value types: https://blog.knatten.org/2018/03/09/lvalues-rvalues-glvalues.... For some reason I never bothered to look up these terms as they sounded so obscure. Turns out the taxonomy is pretty clear and it's just a refinement of the existing two value types (lvalues and rvalues).
EDIT: I do think the naming is rather confusing and inconsistent, though.
lvalue/rvalue are not defined by their movability. value categories are about identity vs non-identity. a pr value is a pure result that has no identity. you cannot reference it by name, and everything else is a consequence of that.
Hmmm, it's pretty clear but why the reminder? I read the article hoping to find some enlightenment, something that we can actually do (better) with this information in mind. But I still miss it. Does anybody know?
21 comments
[ 5.0 ms ] story [ 43.5 ms ] threadThere is a pretty good stack overflow post that quuxplusone linked below. How they explain it:
Now, if you don't know what "move semantics" is, then "lvalues can't be moved from" isn't terribly helpful, and if you do then it's tautological, so I'm not saying you're wrong to criticize. :) But in a C++ context, "move" does have a single specific meaning — the one he's using properly if opaquely-to-non-C++ers.
But here's the gist: sometimes you have an object you want to copy, but then abandon the original. Maybe it's to return an object from a function. Maybe it's to insert the object into a larger structure. In these cases, copying can be expensive and it would be nice if you could just "raid" the original object to steal bits of it and construct the "copy" out of the raided bits. C++11 enabled this with rvalue references, std::move, and rvalue reference constructors.
This added a lot of "what the hell is this" to C++ code and a lot of new mental-model stuff to track for programmers. I understand why it was all added, but I have deep misgivings about the added complexity.
[1] https://blog.knatten.org/2018/03/09/lvalues-rvalues-glvalues...
It makes sense that C++ avoids unnecessary copying or object creation whenever possible, that's pretty much C++'s M.O.
Old habits die hard? It used to always create a temporary right?
Temporaries could still be elided in some cases before, but the semantics were still understood in terms of temporary objects.
Now some forms of elision are mandatory and elision+RVO semantics are understood as objects being directly created into the final named location.
I find that understanding how memory is layed out in executable, how the C works in terms of stack, symbols etc is the introductory knowledge I had to obtain to even think about C++. Not sure what's there now, because I saw recently some great compiler warnings, but I'm pretty sure that I did convert a prvalue to a pointer reference (&) at least once in my life and later failed with memory problems, but no compiler errors
C++ needs those extra categories, they exist for good reasons. But it is more complex.
Similarly just because a compiler may optimize a prvalue away doesn't change the fact that a prvalue by definition of the language is a temp.
While there was only lvalue, and rvalue - it was easy - LEFT and RIGHT - gives you the right intuition.
But now - if it has identity - "glvalue" - why not "ivalue" or "idvalue"?
And then can be moved - "rvalue" ? Why "rvalue", why not "mvalue", or "move-value"? Why the language have to be so constricted when comes to such an important bit - I mean we spell "value" fully, but miss the important bit...
Anyway just a rant... I'm having the same issue understanding "math" sometimes because of all the cryptic notations, and archaic symbols used.
I also enjoyed the linked article about the 5 value types: https://blog.knatten.org/2018/03/09/lvalues-rvalues-glvalues.... For some reason I never bothered to look up these terms as they sounded so obscure. Turns out the taxonomy is pretty clear and it's just a refinement of the existing two value types (lvalues and rvalues).
EDIT: I do think the naming is rather confusing and inconsistent, though.
"Moves are Broken" or why non-destructive moves are at odds with invariants