Passing Functions to Algorithms
std::function is a type that can hold any callable object, so one variable can refer to a function, a function object, or a lambda expression.
A Variable That Changes What It Calls
Programs often need to store an operation and call it later. The operation might be a named function, an object that behaves like a function, or a small unnamed function written in place. C++ provides std::function for this purpose. It is a type that can hold any callable object, so a single variable can stand in for several different kinds of operation over the course of a program.
The best way to see this idea is to watch one variable change what it refers to. The example below uses a variable named f of type std::function. It is assigned three different callables in turn, and the same call expression f(7.0) is written each time. Read the code first and work out what each call should print before reading the explanation that follows.
What do you think happens?
Before reading on, what does each of the three f(7.0) calls print, in order?
Reveal answer
3, then 14, then 7
The first call runs half, which computes 7.0 divided by 2, giving 3.5, and the int conversion gives 3. The second call runs the Doubler object, which computes 14.0 and gives 14. The third call runs the lambda, which computes 7.5 and gives 7. The call text f(7.0) never changed, but the callable behind f did.
What std::function Can Hold
std::function is a type that can hold any callable object, so one variable can refer to a function, a function object, or a lambda expression.
The important word is hold. The variable does not run anything by itself. It stores a reference to one callable at a time, and that stored callable is what runs when the variable is called. Each assignment to the variable replaces what it refers to. In the example, f first holds the plain function half, then the Doubler object, then the lambda. The variable's type is fixed for the whole program, while the callable inside it changes.
The variable's declared type also limits what can be assigned to it. In this example the declared type takes a double and returns an int, so each callable stored in f must be usable in that way. The source does not show a general rule for matching signatures, so treat this as a property of the declaration in this example rather than a complete rule for every case.
Tracing f Through Three Assignments
Tracing means following the program one step at a time and recording which callable f refers to at each call. Start with the first assignment, then follow the calls in order. The sequence below shows each message from the main program to the variable and to the callable it currently holds.
Tracing the Three Calls
For the program above, identify which callable runs at each call of f(7.0) and what it returns.
- 1Step 1: first assignment
f is assigned half. The variable now refers to the plain function half.
- 2Step 2: first call
half runs with 7.0. It computes 7.0 divided by 2, which is 3.5, and the int conversion gives 3.
- 3Step 3: second assignment
f is reassigned to a Doubler object. The old reference to half is replaced, and the call text is unchanged.
- 4Step 4: second call
The Doubler object runs with 7.0. It computes 14.0 and returns 14.
- 5Step 5: third assignment and call
f is reassigned to the lambda. The lambda runs with 7.0, computes 7.5, and the int conversion gives 7.
The program prints 3, then 14, then 7.
3
14
7Three Kinds of Callable Objects
The example shows three kinds of callable objects, introduced one at a time below. Each is assigned to the same variable f. The calling code written as f(...) does not change, even though what f refers to does. The comparison diagram places the three kinds side by side, each paired with the same call syntax.
Finding Where a Result Goes Wrong
Suppose you read a program that reassigns f partway through and expect the old behavior to continue. The trace below shows how to locate the step where the output stops matching your expectation.
Suppose a learner writes f = half, calls f(7.0) and gets 3, then writes f = Doubler{} and calls f(7.0) again, expecting 3 because half came first. The output is 14. Tracing the assignments in order shows the departure point is the second assignment. The call text f(7.0) is identical in both places, so the call itself does not explain the change. The most recent assignment, f = Doubler{}, decided which callable ran, and the expectation carried over from the earlier half assignment was the wrong assumption to examine.
Mistakes With Reassigned Callables
Common mistake Assuming the call site shows which callable runs
What it looks likeReading f(7.0) and assuming it always runs half because half appeared earlier in the program.
Why it failsThe call syntax stays the same for every kind of callable. Whatever f refers to is chosen by the most recent assignment, not by the call text.
Do this insteadFind the last assignment to f before the failing call and identify which callable it stored.
Common mistake Trusting a comment over the actual assignments
What it looks likeA comment says f still holds the plain function, but a later line reassigns f to a function object.
Why it failsA comment can be out of date. The assignment statements are what the program actually executes.
Do this insteadTrace the assignments to the variable in order, one at a time, and check each against the code.
Common mistake Treating a lambda as a named function that keeps its name after assignment
Practice Tracing a Callable Variable
Using the half function and the Doubler object from this article, assign f to half, then to Doubler{}, then to a lambda that returns static_cast<int>(x + 0.5) for a double x. Then predict what f(3.0) prints after the final assignment. Write your answer before checking the hints.
Hints
- Only the most recent assignment matters for the final call, so ignore the earlier assignments when computing the result.
- The lambda computes 3.0 plus 0.5, which is 3.5, and the int conversion keeps only the whole part.
- Check your answer by writing out the value of x + 0.5 before the cast.
Key Takeaways
- std::function is a type that can hold any callable object, so one variable can refer to a plain function, a function object, or a lambda expression.
- Each assignment to a std::function variable replaces what it refers to, while the call syntax f(...) stays the same.
- A lambda expression defines a small anonymous function in place, with a capture list, a parameter list, and a body.
- When a result looks wrong, trace the assignments to the variable in order and identify which callable is active at the failing call rather than trusting comments.