Function Objects and Operator Overloading
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.
Predicting What a Reassigned Variable Does
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 idea is easiest to appreciate by watching one variable change what it refers to. Before reading on, read the code below and work out what each call should print.
4
30What do you think happens?
What do the two calls to f(3) print, in order?
Reveal answer
4, then 30
The first assignment makes f refer to a lambda that adds 1, so f(3) gives 4. The second assignment replaces that lambda with one that multiplies by 10, so the same call f(3) gives 30. The call written as f(3) is identical both times; only the assignment before it changed.
If your prediction differed, that is useful. The gap between what you expected and what the program printed tells you exactly which assumption to examine. The most common assumption to check is whether the call site itself changed, which it did not.
One Variable, Several Callables
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 detail is that the variable does not hold a single fixed operation. Each assignment to the std::function variable replaces what it refers to. The call syntax f(...) stays the same no matter which callable is currently stored, so code that calls f does not need to know which kind of operation is behind it.
The diagram shows the relationship. The wrapper f is the only thing the calling code touches. At any moment it forwards the call to exactly one of the stored callables, namely the one set by the most recent assignment. The three targets are alternatives: f does not refer to all of them at once.
Three Kinds of Callable Objects
The example introduces three kinds of callable, each assigned to the same variable f. They are a plain function, a function object, and a lambda expression. The table compares them by what each one is and how it is invoked.
| Kind | What it is | How it is called through f |
|---|---|---|
| Plain function | A named function defined once and referred to by its name | f(...) runs the named function after f is assigned to it |
| Function object | An object that behaves like a function and can be stored where a function is expected | f(...) runs the object's behavior after f is assigned to it |
| Lambda expression | A small anonymous function defined in place, with a capture list, a parameter list, and a body | f(...) runs the lambda's body after f is assigned to it |
All three rows use the same call site, f(...). Whatever sits behind f is chosen by the most recent assignment.
The source treats these three as interchangeable from the point of view of the caller. Storing callables in std::function is what lets one operation parameter accept a plain function, a function object, or a lambda without changing the receiving code. The title of the topic also names operator overloading; this lesson concentrates on the calling pattern that all three kinds share, and on how the variable's current value determines behavior.
Tracing a Sequence of Reassignments
The most useful skill with std::function is tracing. 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 that describe what the code is supposed to do. The example below uses three callables: a plain function, a function object, and a lambda. Each assignment is labeled so you can follow it.
a = 10, b = 8, c = 1Following f through three assignments
Starting from the code above, determine the value of a, b, and c, and identify which callable answers each call.
- 1Assignment A
f is set to the plain function twice. The call f(5) runs twice with x = 5, giving 2 * 5 = 10. So a = 10.
- 2Assignment B
f is replaced by a function object AddN with n = 3. The call f(5) now runs the object's behavior with x = 5, giving 5 + 3 = 8. So b = 8. The call site f(5) is unchanged.
- 3Assignment C
f is replaced by a lambda that subtracts 4. The call f(5) runs the lambda with x = 5, giving 5 - 4 = 1. So c = 1.
- 4Locating the departure
If you expected a to be 5 because f(5) looks like a plain identity call, the departure happened at step A. Nothing in the call tells you which callable runs; only the assignment does.
a = 10, b = 8, c = 1. Each result comes from the callable set by the assignment immediately before it.
The sequence diagram shows the same caller writing the same call three times. Only the active callable changes between calls, and it changes only when an assignment runs. The step where a result departs from expectation is always the call that follows the assignment that changed the active callable.
Mistakes When Reading std::function Code
Common mistake Assuming the call site determines which callable runs
What it looks likeReading f(5) in the middle of the code and assuming it always runs the plain function twice.
Why it failsThe text f(...) stays the same across all three kinds of callable. What it runs is chosen by the most recent assignment to f.
Do this insteadFind the last assignment to f before the call and identify that callable.
Common mistake Trusting comments over the assignments
What it looks likeA comment says the variable still doubles its input, but a later line reassigns f to a lambda that subtracts.
Why it failsA comment describes intent at the time it was written. The assignments are what actually run.
Do this insteadTrace the assignments to the variable in order and treat them as the source of truth.
Common mistake Expecting an earlier callable to keep running after reassignment
What it looks likeAfter f = AddN{3}, expecting f(5) to still return 10 from the plain function.
Why it failsEach assignment replaces what f refers to, so the earlier callable is no longer active.
Do this insteadTreat every assignment as a replacement, not an addition.
Common mistake Forgetting that a lambda defined in place is still a callable value that f can hold
Passing Stored Operations to Other Code
The source example ends by passing f to std::transform. This is a common pattern: an operation is handed to another piece of code, which calls it later. The source states that functions are useful for callbacks and for passing operations as arguments. Because f is a std::function, the receiving code only needs to know that it can call f; it does not need to know whether f currently holds a function, a function object, or a lambda.
Practicing the Trace
Using the tracing method from this lesson, determine what the following sequence prints, identifying which callable answers each call. Write down your answer before checking it. Code: std::function<int(int)> f = [](int x) { return x + 2; }; print f(1); f = twice; print f(1); f = [](int x) { return x * x; }; print f(1). Here twice is the plain function defined earlier in this article.
Hints
- Find the last assignment to f before each print statement.
- The first print uses the lambda that adds 2, so f(1) gives 3.
- For the second print,
twice(1)is 2 times 1. - For the third print, the last lambda squares its input.
Key Takeaways
- std::function is a type that can hold any callable object, so one variable can refer to a free 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 has a capture list, a parameter list, and a body enclosed in braces, and it can be stored in f like any other callable.
- When a result looks wrong, trace the assignments to the variable in order and identify the callable active at the failing call instead of trusting comments.
- Storing callables in std::function lets code that receives an operation accept a function, a function object, or a lambda without changing.