Concepts / Function Objects and Operator Overloading

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.

  • Programming

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.

cpp
Output
4
30

What do you think happens?

What do the two calls to f(3) print, in order?

Reveal answer
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

Definition

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.

f (std::function)the variable callers useFree functionnamed functionFunction objectobject that acts like afunctionLambda expressionunnamed function written inplacerefers to, when assignedrefers to, when assignedrefers to, when assigned
How can a single variable f refer to a free function, a function object, and a lambda, and what does the wrapper contain versus what it forwards to?

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.

KindWhat it isHow it is called through f
Plain functionA named function defined once and referred to by its namef(...) runs the named function after f is assigned to it
Function objectAn object that behaves like a function and can be stored where a function is expectedf(...) runs the object's behavior after f is assigned to it
Lambda expressionA small anonymous function defined in place, with a capture list, a parameter list, and a bodyf(...) 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.

cpp
Output
a = 10, b = 8, c = 1
Worked example

Following f through three assignments

Starting from the code above, determine the value of a, b, and c, and identify which callable answers each call.

  1. 1
    Assignment 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.

  2. 2
    Assignment 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.

  3. 3
    Assignment 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.

  4. 4
    Locating 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.

Result

a = 10, b = 8, c = 1. Each result comes from the callable set by the assignment immediately before it.

Calling codewrites f(5) each timefstd::function variableActive callableset by last assignment1. assign twice (A)2. call f(5) gives 103. assign AddN{3} (B)4. forwards f(5) gives 85. assign lambda x-4 (C)6. forwards f(5) gives 1
When the same variable f is called several times, which callable runs each call, and at which step does a returned value stop matching what was expected?

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 like

    Reading f(5) in the middle of the code and assuming it always runs the plain function twice.

    Why it fails

    The 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 instead

    Find the last assignment to f before the call and identify that callable.

  • Common mistake

    Trusting comments over the assignments

    What it looks like

    A comment says the variable still doubles its input, but a later line reassigns f to a lambda that subtracts.

    Why it fails

    A comment describes intent at the time it was written. The assignments are what actually run.

    Do this instead

    Trace 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 like

    After f = AddN{3}, expecting f(5) to still return 10 from the plain function.

    Why it fails

    Each assignment replaces what f refers to, so the earlier callable is no longer active.

    Do this instead

    Treat 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

EASY

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.

Related concepts

More concepts starting with F