In modern C++ programming, higher-order functions, which are functions that take other functions as arguments, are fundamental tools. They make your programs more flexible, reusable, and modular.
Our goal for this lesson is to learn how to implement a function that takes another function as an argument. This concept is crucial for custom algorithms and many standard library functions in C++. Ready to dive in? Let's start!
Before we delve into the main topic, let's quickly recap function pointers and std::function
from the <functional>
library. These tools let us pass functions as arguments to other functions, adding flexibility to our code design.
std::function
: This modern tool encapsulates any callable target (functions, lambda expressions, bind expressions, or other function objects) with a specific signature.Why pass functions as arguments? Imagine working on a vector of integers and needing to filter out certain elements. You can create a generic function that takes another function (the filter criterion) to handle the filtering. This avoids code duplication and makes your logic clear and concise.
Let's see this in action by filtering elements from a vector. We will implement a filterVector
function that takes a vector and another function, which defines the filtering rules:
C++1#include <iostream> 2#include <vector> 3#include <algorithm> 4#include <functional> 5 6// Function that takes another function to filter vector elements 7void filterVector(const std::vector<int>& vec, std::function<bool(int)> filterFunc) { 8 std::vector<int> filteredVec; 9 for (int elem : vec) { 10 if (filterFunc(elem)) { 11 filteredVec.push_back(elem); 12 } 13 } 14 15 std::cout << "Filtered Elements: "; 16 for (int elem : filteredVec) { 17 std::cout << elem << ' '; 18 } 19 std::cout << '\n'; 20}
Let's break down the implementation:
Function Prototype:
C++1void filterVector(const std::vector<int>& vec, std::function<bool(int)> filterFunc);
This specifies that filterVector
takes a vector of integers and a function returning a bool
.
Vector Declaration:
C++1std::vector<int> filteredVec;
This declares a vector to store filtered elements.
For-Loop and Filter Function Call:
Then, the loop iterates through each element of the input vector, applying filterFunc
. If an element satisfies the condition, it is added to filteredVec
. Finally, the filtered elements are printed.
Before continuing, let's briefly discuss the auto
keyword in C++. Introduced in C++11, the auto
keyword allows the compiler to automatically deduce the type of a variable from its initializer. This makes your code cleaner and often easier to read by removing redundancy:
C++1auto x = 10; // int 2auto y = 3.14; // double 3auto z = "Hello"; // const char*
When working with functions that return complex types, such as lambdas, auto
can simplify variable declarations. This is especially useful when the exact type is cumbersome to write out.
Let's look at an example of using the function implemented previously:
C++1int main() { 2 std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 3 4 // Filter even numbers 5 auto isEven = [](int n) { return n % 2 == 0; }; 6 filterVector(numbers, isEven); 7 // Output: Filtered Elements: 2 4 6 8 10 8 9 // Filter numbers greater than 5 10 auto greaterThanFive = [](int n) { return n > 5; }; 11 filterVector(numbers, greaterThanFive); 12 // Output: Filtered Elements: 6 7 8 9 10 13 14 return 0; 15}
In this main
function:
numbers
with integers from 1 to 10.isEven
lambda to identify even numbers. This will be our boolean function that defines the filtering rules. We use auto
type to make the function creation easier.filterVector
with isEven
to filter and print even numbers.greaterThanFive
to filter and print numbers greater than 5.Functions that take other functions as arguments are highly flexible. You can easily swap out the function argument to handle different scenarios. Here are more examples:
Filtering odd numbers:
C++1auto isOdd = [](int n) { return n % 2 != 0; }; 2filterVector(numbers, isOdd); 3// Output: Filtered Elements: 1 3 5 7 9
Filtering numbers within a range:
C++1auto withinRange = [](int n) { return n > 3 && n < 8; }; 2filterVector(numbers, withinRange); 3// Output: Filtered Elements: 4 5 6 7
Each example shows adapting filterVector
to different needs by changing the function passed as an argument.
Pros:
filterVector
can filter numbers based on various criteria without needing separate functions for each case.Cons:
auto
, you might encounter more complex compile-time errors.Great job! You've learned to implement a function in C++ that takes another function as an argument. This skill is invaluable for writing flexible, reusable, and modular code. We covered:
std::function
to pass functions as arguments.filterVector
).Now it's time for hands-on practice! You'll implement your own functions that take other functions as arguments. These exercises will solidify your understanding and allow you to experiment with different use cases. Happy coding!