First Class Functions

Learn about first-class functions in C++: a feature that lets you store functions in variables, pass them to other functions, and return them, opening up new design possibilities

Ryan McCombe
Updated

Much of the content of this series relates to a programming concept known as first-class functions.

A programming language supports first-class functions if it allows functions to be treated like any other type of data. For example:

  • Functions can be stored as variables, class members, or within a container such as an array
  • Functions can be passed as arguments to other functions
  • Functions can be returned from other functions

C++ supports first-class functions in several different ways. In this chapter, we will cover the three main options we have: function pointers, function objects, and lambdas

But first, let's see a scenario where first-class functions are a useful feature

A Motivating Example

Let's imagine we have a Party class, which is what enables our Player objects to group up and take on bigger challenges.

class Player {
 public:
  bool isAlive() const { return true; }
};

class Party {
 public:
  Player PlayerOne;
  Player PlayerTwo;
  Player PlayerThree;
};

Elsewhere in our code, we'll want to find out more information about this party. For example, we might want to check that everyone is alive:

#include <iostream>

class Player {/*...*/};
class Party {/*...*/}; int main() { Party MyParty; bool isEveryoneAlive { MyParty.PlayerOne.isAlive() && MyParty.PlayerTwo.isAlive() && MyParty.PlayerThree.isAlive() }; if (isEveryoneAlive) { std::cout << "Everyone is alive"; } }
Everyone is alive

Aside from requiring quite a lot of code, this implementation also makes a big assumption - it assumes the party will always have exactly 3 members. In reality, there may be fewer, and we'd want to manage which Player slots are used within the Party class, rather than making external code responsible.

Worse, if we later expand our Party class to allow additional characters, code like this may still compile, but return the incorrect result because it's not checking if the new PlayerFour is alive.

The root cause here is that our Party isn't encapsulated properly. External code shouldn't be able to poke around the internal workings of our objects like this.

The Party class should take care of details like iterating over its members, and simply expose a friendly public method to access that behaviour.

We covered the importance of encapsulation, and how to implement it, in our beginner course:

Encapsulation and Access Specifiers

A guide to encapsulation, class invariants, and controlling data access with public and private specifiers.

Using Class Methods

Lets implement this isEveryoneAlive() function as a class method. Let's also make all of our Player members private to make our interface simpler.

Then, our code can just call MyParty.isEveryoneAlive():

#include <iostream>

class Player {/*...*/}; class Party { public: bool isEveryoneAlive() { return PlayerOne.isAlive() && PlayerTwo.isAlive() && PlayerThree.isAlive(); } private: Player PlayerOne; Player PlayerTwo; Player PlayerThree; }; int main() { Party MyParty; if (MyParty.isEveryoneAlive()) { std::cout << "Everyone is alive"; } }
Everyone is alive

That is better, but we've solved this problem in a very specific way - we're only allowing users of our class to determine if everyone is alive. There are many possible questions people will want to ask of our party.

For example, the outside world might need to know if everyone in the party is online, or above a specific level, or to check if our party has someone who can fill a specifc role:

MyParty.isEveryoneOnline();
MyParty.isEveryoneAtLeastLevel(50);
MyParty.isAnyoneAHealer();

We can't predict everything people might want to use our class for, and we shouldn't need to write code for all of those anyway. Thankfully, this is one of the problems first-class functions were designed to solve.

Using Predicates

To solve this problem more flexibly, let's first create a standalone function that implements the check we want to do on each member of the party:

bool PlayerIsAlive(const Player& P) {
  return P.isAlive();
}

A function like this that receives an argument, and returns a boolean representing whether or not that argument satisfies a specific condition is sometimes called a predicate. In this example, our predicate is met if the Player we pass as an argument is alive.

To run this check on everyone in the party, we'd like some way to pass this function to a method on the Party class. This method could then invoke this function for every Player in our Party, and return true if every invocation of this predicate returned true.

For example, a method that checks if every Player meets some condition could be called all_of() and, to check if everyone in our Party is alive, the expression might look something like MyParty.all_of(PlayerIsAlive):

#include <iostream>

class Player {/*...*/}; class Party { public: bool all_of(auto Predicate) { return Predicate(PlayerOne) && Predicate(PlayerTwo) && Predicate(PlayerThree); }
private: }; bool PlayerIsAlive(const Player& P) { return P.isAlive(); } int main() { Party MyParty; if (MyParty.all_of(PlayerIsAlive)) { std::cout << "Everyone is alive"; } }
Everyone is alive

The previous example was accomplished using a function pointer, which we cover in more detail in the next lesson.

The beauty of this design is that we can now ask anything of our Party, without the class needing to be expanded or modified.

The outside world provides the function they want to call, whilst our class can handle the iteration, ensuring that function is called for every Player it is managing:

#include <iostream>

class Player {/*...*/};
class Party {/*...*/}; bool PlayerIsAlive(const Player& P) { return P.isAlive(); } bool PlayerIsOnline(const Player& P) { return P.isOnline(); } bool MinLevel50(const Player& P) { return P.GetLevel() >= 50; } int main() { Party MyParty; if (MyParty.all_of(PlayerIsAlive)) { std::cout << "Everyone is alive"; } if (MyParty.all_of(PlayerIsOnline)) { std::cout << "\nEveryone is online"; } if (!MyParty.all_of(MinLevel50)) { std::cout << "\nNot everyone is level 50+"; } }
Everyone is alive
Everyone is online
Not everyone is level 50+

Over the coming lessons, we will see more scenarios where we can apply designs like this and some additional ways they can be implemented in C++

In this lesson, we used function pointers, which are only one of several possibilities. We cover function pointers in more detail in the next lesson.

Summary

In this lesson, we introduced the concept of first-class functions, and one of the ways in which C++ allows us to implement this design. The key things we learned include:

  • First-class functions allow functions to be stored as variables, passed as arguments, and returned from other functions.
  • C++ supports first-class functions in several ways, including function pointers.
  • Predicates are functions that return a boolean value based on whether a condition is met.
  • The practical example using an all_of() method demonstrated some of the flexibility that first-class functions can provide.
Next Lesson
Lesson 64 of 128

Function Pointers

Learn about function pointers: what they are, how to declare them, and their use in making our code more flexible

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Returning Function Pointers from Functions
How can I return a function pointer from a function in C++? What are some use cases for doing this?
Capturing Local Variables in C++ Lambdas
How can I capture local variables from the enclosing scope when using lambdas in C++? What are the different capture modes and when should I use each?
Downcasting Function Pointers in C++
Is it possible to downcast a function pointer to a more specific function pointer type in C++? For example, casting a pointer-to-base to a pointer-to-derived. If so, how is it done and what are the risks?
Function Pointers and Virtual Functions
How do function pointers interact with virtual functions in C++? Can I take a pointer to a virtual function?
Understanding Complex Function Pointer Syntax
C++ function pointer syntax can get quite complex, especially with things like typedef and using directives. How can I decipher complex function pointer syntax?
Function Pointers and Static Member Functions
Can I take a function pointer to a static member function in C++? How is it different from taking a pointer to a non-static member function?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant