When a function returns an lvalue reference, it’s essentially providing a new alias for something that’s already alive.
There’s absolutely no copy involved here.
Since what leaves the function is essentially just another name, any read and subsequent writes to that name affect the original memory directly.
The trick to declare this in the function is to put the & symbol on the return type of the function.
int& getScore(int& score)
{
return score;
}
If the function gets one of its parameters by reference, it could return that same reference directly back to the caller. This means the caller gets another reference, another name that’s pointed to that same object they originally passed in.
If that returned reference is non const then anything the caller reads and writes through that new reference will affect the original object.
The reference returned from the function is simply a name for the real object. The function returning the reference doesn’t actually pass a reference to a copy of the referenced value, nor does returning ownership of the reference hold alive the object the reference points to.
As such, for the reference returned to be valid, the referenced object needs to exist both during the lifetime of the call and the subsequent lifetime of the reference when the call finishes.
By far, the easiest way to guarantee this is for the function to return a reference parameter, because this object clearly survives.
Any references to the object have come from the original caller so returning reference is still part of that original access. A returned non const reference allows read modify write of the original object and read for a const return reference from the referenced objects of object level members.
A function can also take an argument that it receives by non const lvalue reference, and return a non const lvalue reference. In this case, when the function is called and the call stack forms, the parameter becomes an alias for the argument provided to the function.
Any modification which takes place through the function's parameter directly modifies the argument it referenced originally.
Then the function just returns the alias.
No other data is being exchanged between calling code and the function. A non const returned reference allows reading and writing access of the original object from the calling side because the same memory is accessed by means of the parameter and returned reference.
#include <iostream>
int& enlarge(int& value)
{
value = value * 2;
return value;
}
int main()
{
int score = 15;
int& alias = enlarge(score);
std::cout << "After enlarge, score is " << score << '\n';
std::cout << "Alias shows " << alias << '\n';
alias = 100;
std::cout << "After writing through alias, score is " << score << '\n';
return 0;
}
The other scenario is that the function also receives a reference argument by const lvalue reference and returns it as a non const lvalue reference (a valid situation, because a const reference could point to something which is not const). Of course, this makes more sense when the function receives the parameter by const lvalue reference, returning it as a const lvalue reference to allow only reading data without modifying.
That said, it seems to me that it’s really uncommon to take by const references if you are to return it, you will rather use the copy. The advantage here will come for cases like access members which are const, in the function's context, this would be possible but in the context of the caller's reference, it could only view it.
const int& inspect(const int& value)
{
return value;
}
The returned reference can be assigned to a reference of the same type to make the reference itself a reference to the same underlying memory. Any changes to the returned value would be immediately seen in the original object and original references of that object.
The returning of the value cannot modify the object, as the returning entity has no possession/control.
#include <iostream>
int& enlarge(int& value)
{
value = value * 2;
return value;
}
const int& inspect(const int& value)
{
return value;
}
int main()
{
int score = 15;
int& alias = enlarge(score);
std::cout << "After enlarge, score is " << score << '\n';
std::cout << "Alias shows " << alias << '\n';
alias = 100;
std::cout << "After writing through alias, score is " << score << '\n';
const int& view = inspect(score);
std::cout << "View shows " << view << '\n';
// view = 200;
return 0;
The function enlarge takes as argument a non-const reference value. It doubles whatever object the reference refers to and then returns the reference. Since a reference is simply another name, the modification is made directly to the caller's variable, and the returned reference allows the caller to continue use of the same object under the new name.
Here's another function called inspect that takes a const reference, also called value.
It just returns that reference. The const remains on the returned name so that whoever calls the function can look at the object, but not change it.
In main, an ordinary integer called score is created and initialized to 15. The call enlarge(score) binds the reference parameter within enlarge directly to score.
Enlarge doubles score to 30 and returns a reference to it. That returned reference is used to instantiate alias, so that now score and alias both refer to the same integer.
The first two to be printed show that each name sees the value 30. The later line alias = 100 writes over the name alias. As alias is simply another name for score, score immediately becomes 100. This is confirmed by the next print statement.
The call inspect(score) returns a const reference that gets stored in the name view. View then is yet another alias to score, but the const here forbids any write through it. The last print shows 100 through view. The comment line attempting to assign to view is illegal; the compiler refuses it on account of view being const.
There are additional names (each refers to the score variable) with every reference in the program. No additional integers are created once the score has been instantiated.
Exercise: Returning by ReferenceStarter code, hints and solutionOpenClose
Practice: Returning by Reference
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Create other with value 7 and bind a reference to enlarge(other). Print the new value, then assign 18 through that returned reference and print other again.
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>
int& enlarge(int& value)
{
value = value * 2;
return value;
}
const int& inspect(const int& value)
{
return value;
}
int main()
{
int score = 15;
int& alias = enlarge(score);
std::cout << "After enlarge, score is " << score << '\n';
std::cout << "Alias shows " << alias << '\n';
alias = 100;
std::cout << "After writing through alias, score is " << score << '\n';
const int& view = inspect(score);
std::cout << "View shows " << view << '\n';
// view = 200;
return 0;
}
Hints
enlarge doubles the caller's score and returns a reference to that same score. Its returned alias remains valid because score still lives in main.
Store enlarge(other) in an int& variable while other is alive in main. Assignment to the returned alias then changes other, not a new copied int.
Worked solution
The new call enlarge(other) doubles other from 7 to 14 and returns an alias to that same variable. Assigning 18 through the returned reference changes other to 18. The object remains alive in main throughout.
#include <iostream>
int& enlarge(int& value)
{
value = value * 2;
return value;
}
const int& inspect(const int& value)
{
return value;
}
int main()
{
int score = 15;
int& alias = enlarge(score);
std::cout << "After enlarge, score is " << score << '\n';
std::cout << "Alias shows " << alias << '\n';
alias = 100;
std::cout << "After writing through alias, score is " << score << '\n';
const int& view = inspect(score);
std::cout << "View shows " << view << '\n';
// view = 200;
int other{7};
int& otherAlias = enlarge(other);
std::cout << "Other after enlarge: " << other << '\n';
otherAlias = 18;
std::cout << "Other after alias write: " << other << '\n';
return 0;
}
Example output
After enlarge, score is 30
Alias shows 30
After writing through alias, score is 100
View shows 100
Other after enlarge: 14
Other after alias write: 18
0 comments
No comments yet. Be the first.