Normal lvalue references won't take temporaries.
That limit keeps names from dangling, but it does keep us from a useful pattern: where a transient needs to hang around long enough to get initialized before it is discarded.
This is precisely what an rvalue reference is designed for.
It is declared with two &.
int&& tempAlias = 42;
An integer literal like 42 is a prvalue,
which is a kind of rvalue. The value category explains whether the
expression references an object (a prvalue does not).
We do not want to define rvalues just as not referring to objects, because xvalues are rvalues that do refer to objects.
Ref. 18 When the reference is bound to the temporary, the lifetime of the temporary is extended to match that of the reference.
As the reference is non const, the temporary can also be changed using that name.
A non const lvalue reference needs a non const lvalue.
An rvalue reference requires an rvalue.
Thus, the two reference types do not take the same sorts of expressions.
The language chooses the correct reference based on the type of argument provided.
A const lvalue reference is more flexible as it can bind to both lvalues and rvalues.
Note that a const lvalue reference is not modifiable using the reference name.
An rvalue reference is the form that accepts a temporary (so you can pass a temporary to the method), while still allowing the temporary to be modified.
You can declare a parameter as an rvalue reference in a function.
Where an argument is bound to an rvalue reference parameter to a function call, the temporary object for that parameter will stay alive until the end of the entire expression containing the function call.
#include <iostream>
// Bind an rvalue reference to a temporary or movable object
void adjust(int&& number)
{
number = number + 5;
std::cout << "Inside adjust: " << number << '\n';
}
int main()
{
adjust(10);
return 0;
}
The function adjust has a parameter of type int&&. This is an rvalue reference to an int, and lvalues cannot be bound to rvalues, so passing in the number 10 to this function works: adjust(10);. When you pass in the number 10, that is considered a temporary int that is created for that function call, and the parameter number holds an rvalue reference to that temporary int for the duration of the function call.
Inside the function, when number = number + 5; is called, the function first reads from number and gets the value 10. Then it performs 10 + 5. 15 is then assigned to the temporary int that number points to.
Even though the parameter is declared as int&& number, the variable number
can be treated as an lvalue when used in expressions inside the
function because it is a named variable and has an identity, and can
therefore be accessed multiple times even if it is an rvalue reference.
This is why you can use number on both sides of the = sign.
Printing std::cout << "Inside adjust: " << number << std::endl; prints:
Inside adjust: 15
The change to the number is then only performed to the temporary int created by the literal 10. That temporary int lasts the life of the full expression where it is created, so while adjust is running, it exists. When it goes out of scope after the function returns, it is destroyed. There is no object in main that holds the changed value.
If the parameter to the function was passed in as int& number, it would not be able to bind to a temporary int.
void adjust(int& number);
// adjust(10); // Compile error
A non const lvalue reference usually points to an lvalue.
int value = 10;
adjust(value);
A const lvalue reference would work, const int& number, but the value could not be modified through the reference.
void inspect(const int& number)
{
// number = number + 5; // Compile error
std::cout << number << '\n';
}
So in essence, this example shows the difference between an rvalue reference and an lvalue reference and why the two are necessary.
An rvalue reference can also be specified directly in a scope and is bound to a prvalue.
The temporary would normally vanish at the conclusion of the expression that produced it.
However, tying itself to the rvalue reference in this way prolongs its lifetime until the reference itself is out of scope.
The reference name is evaluated, and set to an ordinary modifiable name bound to the temporary.
Current value can be read by using the reference.
It is also possible to assign a new value to it.
The new value persists as long as the temporary exists by virtue of the reference.
#include <iostream>
int main()
{
int&& alias = 20;
alias = alias * 2;
std::cout << "After change through alias: " << alias << '\n';
return 0;
}
Since an rvalue reference is not const, the following assignment is legal.
The right side of an expression can read the state of a temporary, calculate something and generate a replacement value.
Then the assignment writes it back into the temporary using the reference.
The trivial still the same subject.
The only thing that changes is the value stored within;
This is one of the main differences between an rvalue reference and a const lvalue reference.
Both are bindable to a temporary, but only the rvalue reference allows you to modify the bound name.
#include <iostream>
void adjust(int&& number)
{
number = number + 5;
std::cout << "Inside adjust: " << number << '\n';
}
int main()
{
adjust(10);
int&& alias = 20;
alias = alias * 2;
std::cout << "After change through alias: " << alias << '\n';
return 0;
}
The temporary must have actual storage, it can be for as long as it exists.
The rvalue reference does not make another copy of that temporary.
Names the temporary and prolongs its lifetime when lifetime extension rules of the language apply.
Thus all reads and writes through the reference access directly to the temporary's storage.
When the reference comes to the end of its lifetime, the temporary is destroyed.
Returning an rvalue reference to a local object is undefined behavior for the same reason that it is not safe to return an lvalue reference for a local object.
When the function returns, the local object is deleted.
The returned reference is then ended up naming a storage that the owner is no longer a living object.
Note, that the returned type is an rvalue reference to the object; return value itself does not extend its lifetime.
The safety requirement stays the same.
Only while the referent remains alive may a reference be followed.
Rvalue references differ from lvalue references primarily in what they are capable of being bound to.
Allows you to provide a temporary modifiable name and when directly bound, prolongs the temporary?
They do not provide ownership and make it safe to refer to objects whose lives had already ended.
Exercise: Rvalue ReferencesStarter code, hints and solutionOpenClose
Practice: Rvalue References
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Add adjust(30), then assign 50 through the local alias. Predict both results and identify the separate temporaries referred to by the parameter and local reference.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
void adjust(int&& number)
{
number = number + 5;
std::cout << "Inside adjust: " << number << '\n';
}
int main()
{
adjust(10);
int&& alias = 20;
alias = alias * 2;
std::cout << "After change through alias: " << alias << '\n';
return 0;
}Hints
In adjust(int&& number), && declares an rvalue-reference parameter. adjust(10) binds it to a temporary integer that the function can modify.
The local int&& alias = 20 also names a temporary, and alias itself is an lvalue expression when used by name. Changing alias does not require a new allocation.
Worked solution
A second adjust call binds the parameter to a new temporary with value 30 and changes it to 35. The local alias still refers to its own extended-lifetime temporary; assigning 50 through alias changes that separate object.
#include <iostream>
void adjust(int&& number)
{
number = number + 5;
std::cout << "Inside adjust: " << number << '\n';
}
int main()
{
adjust(10);
int&& alias = 20;
alias = alias * 2;
std::cout << "After change through alias: " << alias << '\n';
adjust(30);
alias = 50;
std::cout << "Local temporary: " << alias << '\n';
return 0;
}
Example output
Inside adjust: 15
After change through alias: 40
Inside adjust: 35
Local temporary: 50
0 comments
No comments yet. Be the first.