Function return Statements

Allow our functions to communicate with their caller by returning values when they complete
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
3D art showing a blacksmith character
Ryan McCombe
Ryan McCombe
Updated

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 function that calls 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 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.

But we haven't been returning an integer from main?

The main function gets some special treatment, given its unique status. That is explained later in this lesson.

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 as the operands of operators:

#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();
}

Side Effects

Anything a function does that affects the state of our program aside from returning a value is sometimes referred to as its side effects. For example, updating a variable that is defined outside the body of our function is an example of a side effect.

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.

Returning Multiple Objects from a Function

In C++, and most programming languages, we cannot return multiple objects from a function. A function can only return a single value, of a single type.

Later, we will introduce more complex data types - including things that can be thought of as containers - a single object that can contain other objects inside it.

For now though, just treat our functions as being limited to returning a single object like an int, bool, or float.

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.

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 code:

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() };

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 just refer to the function, such as 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.

But we’re not returning anything from main?

You may note we haven’t been returning 0 (or anything else) from our main function. The reason for this discrepancy, and the reason our code even compiles despite seemingly breaking the rules, is that the main function gets some special treatment.

The C++ specification states:

If control reaches the end of main without encountering a return statement, the effect is that of executing return 0;

Our other functions don't share this property - if they have a return type, they need to return something.

We tend to take advantage of this unique main behavior in our code samples to reduce the amount of syntax students have to look at when learning. But if preferred, feel free to explicitly return 0; from your 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 delved into how to specify the return type of a function, using types like int, float, and void.
  • Using return Statements: The lesson highlighted the importance of the return 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.

Preview of the Next Lesson: Implicit Type Conversions

In the upcoming lesson, we'll shift our focus to a new but related topic: Implicit Type Conversions in C++. This lesson will cover:

  • Understanding Implicit Conversions: Learn what implicit type conversions are and how they work in C++.
  • Safe and Unsafe Conversions: We'll explore which type conversions are safe and which ones can lead to unexpected results.
  • Best Practices: Gain insights into best practices to follow when dealing with implicit conversions.

Was this lesson useful?

Next Lesson

Implicit Conversions and Narrowing Casts

Going into more depth on what is happening when a variable is used as a different type
3D art showing a sorcerer character
Ryan McCombe
Ryan McCombe
Updated
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Next Lesson

Implicit Conversions and Narrowing Casts

Going into more depth on what is happening when a variable is used as a different type
3D art showing a sorcerer character
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved