Function return
Statements
Allow our functions to communicate with their caller by returning values when they complete
So far, we've been imagining functions as standalone blocks of code. They've been, for the most part, isolated from our surrounding program.
The next step in making our functions more useful will be to allow them to communicate with other parts of our code.
Return values are the main way our functions can send some information to the code that called them.
Function Return Types
In our exploration of functions so far, we have waved off the syntax such as void
and int
that we've been putting before our names:
void TakeDamage() {}
int main() {}
In this lesson, we will see what exactly these are for.
You may have recognized int
as a data type that we've seen in other contexts. Indeed, these parts of our function syntax are data types.
We've seen that a function can run any code we need. But, functions can also return information to the code that called it.
With C++ being a strongly typed language, we need to specify what type of data our functions will be returning. That is what the int
and void
in the function headers refer to.
The main
function returns an int
, so we have an int
in the heading of the function. The void
keyword is how we specify that a function doesn't return anything. So, in our TakeDamage()
function, we specified the return type as void
.
In the below example, we are initializing a variable called Level
but, this time, we are initializing that variable to the result of calling a function:
int Level { CalculateLevel() };
For this to work, the act of calling our CalculateLevel()
function must result in an integer value.
We can make this happen in two steps - first, we place the int
keyword before our function name, to declare that this function is going to return an integer:
int CalculateLevel() {
// Code here
}
Secondly, we update the body of our function to ensure it returns an integer, by using the return
keyword:
int CalculateLevel() {
return 5;
}
With that, our initial line of code will now work, and we're able to access what our function call returned at the point we called it.
For example, we could use this return value to initialize a variable:
#include <iostream>
using namespace std;
int CalculateLevel(){ return 5; };
int main(){
int Level{CalculateLevel()};
cout << "Level: " << Level;
}
Level: 5
Or we could directly log out the result of a function call, or use it with any other operator:
#include <iostream>
using namespace std;
int CalculateLevel(){ return 5; }
int main(){
cout << "Level: " << CalculateLevel();
cout << "\nHealth: " << CalculateLevel() * 5;
}
Level: 5
Health: 25
Test your Knowledge
Function Return Values
After running the following code, what will be the value of Health
?
1float GetHealth() {
2 return 100.0;
3}
4float Health { GetHealth() };
After running the following code, what will be the value of isDead
?
bool GetIsDead() {
return false;
}
bool isDead { GetIsDead() };
Using return
Statements
We've seen from our TakeDamage()
function in previous lessons that not all functions need to return something.
In those cases, we have set the return type to be void
:
void TakeDamage() {
Health -= 50;
}
However, let's update this function to return something too. For example, it could return the amount of damage that was inflicted:
int TakeDamage() {
Health -= 50;
return 50;
}
Now, not only does our function have an effect (ie, reducing Health
) - it also returns something, which might be useful in the locations where our TakeDamage()
function is called:
int Score { 0 };
int main() {
int DamageInflicted { TakeDamage() };
// We can now use the returned value
// For example, we could update a scoreboard:
Score += DamageInflicted;
}
We could also have implemented the above example in a single statement:
int Score { 0 };
int main() {
Score += TakeDamage();
}
Note, just because a function returns something, that does not obligate the caller to make use of what is being returned.
If our main
function had no use for the return value, but just wanted to call the function because of its other effects, it can just continue to call the function as it did before:
int main() {
TakeDamage();
}
Test your Knowledge
Updating Variables using Functions
After running this code, what will be the value of Health
?
int Health { 100 };
void TakeDamage() {
Health -= 50;
}
TakeDamage();
After running this code, what will be the value of NewHealth
?
int Health { 100 };
void TakeDamage() {
Health -= 50;
}
int NewHealth { Health };
After running this code, what will be the value of Health
?
int Health { 100 };
int TakeDamage() {
return Health - 50;
}
TakeDamage();
After running this code, what will be the value of Health
?
1int Health { 100 };
2int TakeDamage() {
3 Health -= 50;
4 return 50;
5}
6Health -= TakeDamage();
return
Statements with Conditionals
When using conditional logic, we may need to have multiple return
statements in our code. For example:
bool isDead { true };
int TakeDamage() {
if (isDead) {
return 0;
} else {
Health -= 50;
return 50;
}
}
If our function has a return type, we should make sure that every possible branch through our function results in something of that type being returned.
Early return
Statements
It's crucial to understand that when a function encounters a return
statement, it immediately stops executing and passes control back to the calling function.
Were we to write our TakeDamage()
function like in the following example, Health
would never be reduced, because the function will always have hit the return
statement before hitting line 3:
1int TakeDamage() {
2 return 50;
3 Health -= 50; // Unreachable code
4}
Line 3 is what is sometimes referred to as "unreachable code". Whilst our code can still work, this often indicates we made a mistake. Most IDEs can also detect unreachable code in simple cases like this, and warn us that we could have a problem.
Early Returns with Conditionals
The fact that functions stop executing as soon as a return
call is found is something we can use to our advantage when structuring our code.
As we saw in previous examples, a return
statement can be inside conditional blocks such as an if
statement.
By combining conditional logic with the property that functions stop once they return
, we can make our code a lot more concise. For example, we could reduce this function we saw earlier:
bool isDead { true };
int TakeDamage() {
if (isDead) {
return 0;
} else {
Health -= 50;
return 50;
}
}
To something that does the same thing with less syntax:
bool isDead { true };
int TakeDamage() {
if (isDead) return 0;
Health -= 50;
return 50;
}
In the above example, the else
block has been removed. This is because, if isDead
is true, our function will return
before any code in the else
block would have been reached anyway.
Therefore, having that code within an else
statement is redundant.
Test your Knowledge
Multiple Return Statements
After running this code, what will be the value of Health
?
bool isDead { true };
int GetHealth() {
return isDead ? 0 : 50;
return 100;
}
int Health { GetHealth() }
After running this code, what will be the value of Health
?
bool isDead { false };
int GetHealth() {
if (isDead) {
return 0;
}
return 100;
}
int Health { GetHealth() };
Early Returns with void
Functions
We can also return
early from functions that have a void
return type. To do this, we just use the return
statement without providing any value:
void SomeFunction() {
return;
// Unreachable code
std::cout << "This won't be executed";
}
This is primarily useful for control flow, as an alternative to something like an if
statement.
For example, the following function modifies the Health
variable only if it is greater than zero:
int Health { 100 };
void InflictDamage() {
if (Health > 0) {
Health -= 10;
}
}
We can implement the same behaviour using an early return
statement:
int Health { 100 };
void InflictDamage() {
if (Health <= 0) return;
Health -= 10;
}
Common Mistakes
When it comes to function return values, there are a couple of common mistakes worth highlighting:
return
vs cout
A common misunderstanding with beginners, particularly when creating the basic functions typical of introductory courses, is the difference between logging and returning a value from a function.
The following are not equivalent:
void MyFunction() {
cout << 5;
}
int MyFunction() {
return 5;
}
If a function isn't working as you expect, particularly issues around return values, ensure you are using the return
keyword to return the value - not simply logging it out to the console.
Not Calling The Function
The other common mistake beginners make when trying to use a function is not calling it when they intend to. Rather, they will correctly include the name of the function, but forget to include the ()
operator to invoke it. An example is below:
int GetLevel() {
return 5;
}
int Level { GetLevel };
This will cause a rather cryptic error message. If we see any errors around our use of functions, ensure we are calling the function using ()
:
int GetLevel() {
return 5;
}
int Level { GetLevel() };
Exit Codes - main
's Return Type
Something odd about our main
function might have caught your attention by this point. Our main
function has a return value specified as int
,
This integer returned from our main
function is expected to be an exit code - a number that explains why our main
function ended - and therefore, why our program quit.
Just like our function can return a value to another function that called it, our entire program can return a value to the program that executed it, such as the operating system.
You may have noticed output like this in your terminal while we've been running our code:
The program 'MyProgram.exe' has exited with code 0.
The code 0
here indicates that your main
function returned 0
. Try returning a different value from main
, and see how this output changes.
int main() { return 5; }
The program 'MyProgram.exe' has exited with code 5.
Returning 0
from the main
function announces that our program exited as expected, under normal circumstances. In other words, it did not crash.
When using other software, you may have sometimes seen a program crash, and then your operating system displays a popup to give you the option to report the crash. That popup was most likely triggered by a program not returning 0
from its main
function.
Test your Knowledge
Early Return Statements
After running this code, what will be the value of Health
?
bool isDead { true };
int GetHealth() {
if (isDead) {
return 0;
}
}
int Health { GetHealth() };
Summary
In this lesson, we explored several key aspects of function return statements in C++. Here's a brief overview of what we've covered:
- Understanding Function Return Types: We learned how to specify the return type of a function, using types like
int
,float
, andvoid
. - Using
return
Statements: The lesson highlighted the importance of thereturn
statement in functions and how it passes values back to the calling function. - Functions Without Return Values: We discussed functions with a
void
return type, which do not return any value. - Side Effects: The lesson touched upon how functions can have side effects, such as changing the value of variables outside of their body.
- Conditional Return Statements: We examined how to use return statements within conditional blocks like
if
statements. - Early
return
Statements: The concept of early returns in functions was explained, showing how they can make code more concise. - Common Mistakes: To help avoid common pitfalls, the lesson covered typical mistakes beginners make regarding function return values.
Implicit Conversions and Narrowing Casts
Going into more depth on what is happening when a variable is used as a different type