Lesson 3
Implementing a Function that Returns a Function
Lesson Introduction

Welcome to this lesson on implementing a function that returns another function in C++. By the end of this lesson, you will understand how to create higher-order functions that can return other functions. This concept is useful in scenarios where you want to create customizable or deferred behavior in your programs, such as factory functions that generate specific functions at runtime.

Concept Overview

Higher-order functions either take other functions as arguments or return them. They help create flexible, reusable code. When returning a function, consider it a "function generator" that can create specific functions based on runtime parameters.

This approach is valuable when you need functions with different behaviors based on parameters. Instead of writing multiple similar functions, you only need one generator function.

We'll use std::function and lambda expressions to create a function that returns another function. This is often called a "factory function." Our example will generate incrementing functions based on a given increment value.

Step-by-Step Implementation: Part 1

The incrementor function:

C++
1#include <functional> 2 3std::function<int(int)> incrementor(int increment) { 4 return [increment](int x) { 5 return x + increment; 6 }; 7} 8 9int main() {return 0;}
  • Signature: It returns std::function<int(int)>, indicating it returns a function that takes and returns an int.
  • Return Statement: It returns a lambda that captures increment by value and takes an integer x, returning x + increment.

This way, this function creates another function, which increments its input value by increment, and returns the result.

Step-by-Step Implementation: Part 2

Let's consider the main function:

C++
1#include <iostream> 2#include <functional> 3 4std::function<int(int)> incrementor(int increment) { 5 return [increment](int x) { 6 return x + increment; 7 }; 8} 9 10int main() { 11 auto inc3 = incrementor(3); // Returns a function that adds 3 12 std::cout << "Increment 5 by 3: " << inc3(5) << '\n'; // Output: Increment 5 by 3: 8 13 14 return 0; 15}
  • Function Call: incrementor(3) returns a function that adds 3. We store it in the inc3 variable.
  • Calling the Returned Function: inc3(5) increments 5 by 3, producing 8.
Factory

We can create as many such functions as we want. Here is another example:

C++
1#include <iostream> 2#include <functional> 3 4std::function<int(int)> incrementor(int increment) { 5 return [increment](int x) { 6 return x + increment; 7 }; 8} 9 10 11int main() { 12 auto inc11 = incrementor(11); // Returns a function that adds 11 13 std::cout << "Increment 7 by 11: " << inc11(7) << '\n'; // Output: Increment 7 by 11: 18 14 15 return 0; 16}

This why we call this function factory!

Common Pitfalls

When working with captured variables in lambdas, ensure the captured variables' lifetimes exceed the lambda's usage. Avoid capturing local stack variables in lambdas that outlive their scope. In our previous example, we safely capture the increment value, which is a parameter, ensuring its lifetime during the lambda's execution.

Capturing by value ([=]) or reference ([&]) has different implications. Capturing by value gives the lambda its own copy, while capturing by reference works with the original variable. Incorrect usage can lead to unintended behaviors.

Let's look at an example of incorrect code:

C++
1#include <iostream> 2#include <functional> 3 4std::function<int(int)> faulty_incrementor() { 5 int local_increment = 5; 6 return [&local_increment](int x) { 7 return x + local_increment; 8 }; 9} 10 11int main() { 12 auto inc = faulty_incrementor(); 13 std::cout << "Increment 5 by 5: " << inc(5) << '\n'; // Undefined behavior. It can output anything 14 return 0; 15}

In this example, local_increment is a stack variable local to the faulty_incrementor function. When the lambda is returned, it captures local_increment by reference, but local_increment goes out of scope once faulty_incrementor returns, leading to undefined behavior when the lambda is called.

Lesson Summary

You learned how to create functions that return other functions using std::function and lambda expressions in C++. We discussed why and when to use such higher-order functions and walked through an example of an incrementor factory function. You now have a foundational understanding of creating dynamic and flexible code.

Now that you've grasped the theory, it's time to solidify your knowledge through hands-on practice. You'll work on exercises to create functions that return other functions and use them in various scenarios. Let's get into the practice section to apply what you've learned!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.