Introduction: The Question that Sparked the Lesson
| simplicio | I am still wrapping my head around rvalue references, but I think I am finally starting to get it.
| salviati | rvalue references are really simple once you separate them from "move semantics"
| salviati | rvalue references are just a way to keep a mutable reference to a temporary
| salviati | const lvalue references could already keep const references to them with the same lifetime extension property
| simplicio | I just need to make sure I am clear on when I am calling a copy *constructor* versus when I am using an assignment operator.
| _________ | These are two different concepts in the Rule of Three, and yet I seem to recall I was using one when I thought I was using the other.
| salviati | rvalue references just allowed for mutability
| salviati | Move semantics are just a convention of destroying the source object in a "move constructor" (or move-assign operator)
| KittyCat | more to 'invalidate' than 'destroy', really.
| salviati | Yeah
| salviati | Because of the fact that [lvalue] references decay to [rvalue] references pretty easily, you use std::move (which is just a [wrapper]
| ________ | for a cast-to-rvalue expression) to preserve the reference type when passing them
| salviati | Actually they don't decay pretty easily --
| salviati | Oh they do in cases where you use std::forward
| simplicio | whoa wait... you are saying this so quickly but I always feel a need to slow down when I read these things....
Definition 1: "Mutable Reference"
| simplicio | "mutable reference" ... uhh mutable in what way exactly? As I recall it, rvalue references have no memory location assigned to them, but nonetheless, references in general cannot be "reassigned"
| salviati | std::move is usually used to pass an lvalue as an rvalue reference to have it invalidated as if it were a temporary
| salviati | "mutable-reference" as in "not a const-reference"
| salviati | Or rather, "reference to mutable"
| salviati | References are always constant themselves
| salviati | Or a better way to think about references is that they aren't actually values so the concept of mutability doesn't really apply to them
| salviati | int& and int&& are both "reference to mutable"
| salviati | and const int& is a "reference to const"
| salviati | I don't like that terminology because you can have "references to const" even though the referenced object isn't really const
| salviati | Just like you can have "reference to rvalue" for an object that isn't really an rvalue
| salviati | So const-ref is a reference to an object through which you cannot mutate the referenced object
| salviati | And an rvalue-ref is a mutable reference to an object, through which convention people use to implement move operations
| salviati | (there are no const rvalue references, because const lvalue references work just fine)
<digression>
| KillerWasp | salviati# the constructor and destructor in one line have a name, but i forget it
| salviati | You put your constructor and destructor in just one line?
| salviati | The only named category I can think of that contains both constructors and destructors would be "special member functions"
| KillerWasp | salviati# yes, the object is created and destroyed on the same line, which on the next line no longer exists.
| salviati | Ah, that doesn't have a name AFAIK
| salviati | That is just a temporary object which doesn't have it lifetime extended
| salviati | e.g. in the statement 1; the value is never captured so its just created and destroyed immediately
| salviati | in f(1) it's extended for the life of the call to f(int)
| salviati | and [in] int&& i = 1; it's extended for the lifetime of the name "i"
</digression>
| simplicio | "I don't like that terminology because you can have 'references to const' even though the referenced object isn't really
| _________ | const" ... can you give an example? Are you talking about some crazy kind of reference to a const pointer to a non-const
| _________ | pointer...?
| salviati | No I'm talking about the fact you can make a const reference to a non-const object
| salviati | const int& i = 123; for example
| salviati | or int i; const int& iref = i;
| salviati | You can do int i = 123; f(static_cast(i));
| salviati | f(int&&) is then called
| salviati | Which, by convention, assumes it is allowed to clobber the value of 'i'
Definition 2: "Object"
| simplicio | > Just like you can have 'reference to rvalue' for an object that isn't really an rvalue
| _________ | this confuses me even more...
| _________ | I do not consider `int` to be an object. I consider it to be a primitive type. Am I wrong?
| salviati | It is an object of primitive type
| KittyCat | I wish the language was more strict about reference/pointer-to-const
| salviati | You can also have objects of user-defined types (like structs/classes/enums)
| KillerWasp | salviati# not's Lambda?
| KittyCat | so many optimization opportunities lost basically because of const_cast and aliasing
| salviati | "object" isn't used in C and C++ to refer to high-level concept of "Object" that exists in object-oriented programming
| salviati | An object is basically something that has a value and an address
| salviati | Whether the type of the object is int, or struct { int; float; std::vector; }
| salviati | I guess its better to say an object has a type, a value, and an address
| salviati | A simple variable declaration creates both an object and a name for it
| salviati | int i = 123; -- the object is the conceptual thing that has a type of int, a value of 123, and an address you can retrieve via &i
| salviati | We call "i" a variable, but in fact its just a name referring to that object
Experiment: Memory Clobbering
| simplicio | "Which, by convention, assumes it is allowed to clobber the value of 'i'". Yes, but hold on; as long as `i` is declared as a const reference, then doesn't that imply that it's allocated in non-heap memory...? Err wait hang on, const references work weirdly, I need to check my notes...
| salviati | int&& is not a const reference, its an rvalue reference
| salviati | You can also pass a mutable object via const [reference] and you probably do it all the time
| salviati | This isn't references but: e.g. char a[100]; char b[100]; memcpy(a, b, 100);
| salviati | Here you've passed a const pointer to a mutable char
| salviati | std::string s = "Hello world"; std::string s2(s);
| salviati | Is another example
| salviati | s is a mutable object, but you've passed it via const-reference to call string::string(const string&)
| salviati | This is allowed because the rules for const-references are a subset of the rules for mutable references
| simplicio | wait I think I know why I was confused. You used the same variable as a formal parameter *and* as an actual parameter... But
| _________ | my point is, when you do `int i = 123;`, that's not allocated using the heap. So how could it be possible for the memory to be
| _________ | "clobbered" ?
| salviati | Because you can still overwrite its value
| salviati | Take this for example
| salviati | { char buf[8] = "XXXXXXX"; f(std::move(buf[0])); } f(char&& x) { x = 'Y'; }
| salviati | First note that std::move(buf[0]) is just an alias for static_cast(buf[0])
| salviati | So all we're doing is taking the natural lvalue reference you get when you name "buf[0]" and then casting it explicitly to an rvalue reference -- which is allowed!
| salviati | And then using it to call a function that takes an rvalue reference and by convention will overwrite it with a garbage value
| salviati | Now the value, which is stored only "on the stack" is now "YXXXXXX"
| simplicio | wait.... is that true? Dang... Why didn't anyone tell me.
Extended Examples
| salviati | You can do the same example with an int
| salviati | { int i = 123; f(std::move(i)); /* x is now zero */ } f(int&& x) { x = 0; }
| salviati | A more useful example is when you look at std::string...
| salviati | { std::string s = "Hello"; f(std::move(s)); /* s is now empty, and some_global holds "Hello" */ } f(std::string&& s) { std::swap(s, some_global); } std::string some_global;
| salviati | Though it's worth noting that f() here isn't even guaranteeing that 's' becomes empty
| salviati | Because by convention you assume that 's' is now invalid or in an undefined state
| salviati | Because it's an rvalue reference, the writer of f() is actually expecting you to probably call it like f("Hello") instead
| salviati | Which is going to construct a temporary std::string(), pass it by rvalue reference to f(), which is then able to efficiently move its contents in to some_global
Writing to an Rvalue
| salviati | You can also just straight up write to rvalue reference the same way as lvalue references
| salviati | { int i; int& iref = i; iref = 123; /* i is now 123 */ }
| salviati | This is not surprising
| salviati | { int&& iref = 0; iref = 123; /* the temporary object iref was referencing is now 123 */ }
| salviati | What happens in the second example is lifetime extension, and you can do the same thing with const references
| salviati | { const int& iref = 123; /* ... */ std::cout << iref; } for example is completely fine
| salviati | The lifetime of the object that is referenced here is preserved for as long as the reference is in scope
| salviati | Its the same concept that allows function calls to work
| salviati | int f(const int& i) { std::cout << i; } { f(123); }
| salviati | Here the lifetime of the object created by the expression "123" is preserved until f() returns
Conclusion & Comparison to Other Languages
| salviati | rvalue references are basically all the magic of const references, but with an allowance for mutability.
| salviati | It's easy to miss the magic when you're doing f(123); and calling f(const int&).
| salviati | Even that is something surprising that you have to learn is possible.
| salviati | You never see code in C which stores or passes a pointer to a temporary object produced by an expression
| salviati | Because you'll either get a dangling pointer or the syntax doesn't even work!
| salviati | Maybe f(&g()) is allowed. idk. It's stupid either way.
| salviati | Another way to think of rvalue references is like passing ownership of an object to you, maybe?
| salviati | If C++ was rust for example, it should definitely forbid passing the same object by rvalue reference twice
| merlin | Doesn't C++ explicitly allow that since you're required to keep the movee in a valid state?
| salviati | "the movee"?
| salviati | The thing being moved?
| merlin | Yes
| salviati | I don't think you're required to keep it in a valid state...
The conversation trails off.
|