Concepts / Passing Functions to Algorithms

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.

  • Programming

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.

cpp

What do you think happens?

Before reading on, what does each of the three f(7.0) calls print, in order?

  • 3, then 14, then 7

  • 3, then 3, then 3

  • 7, then 14, then 3

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

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

fstd::function variablehalfplain functionDoubler{}function objectlambdalambda expressionholds at one timeholds at one timeholds at one time
How can one variable of type std::function refer to three different kinds of callable objects, and how does the same call expression reach each of them?

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.

mainfstd::function variablehalfplain functionDoublerfunction objectlambdalambda expressionf = halfcall f(7.0)returns 3f = Doubler{}call f(7.0)returns 14f = lambdacall f(7.0)returns 7
After each call through the std::function variable, which underlying callable runs, and what value comes back?
Worked example

Tracing the Three Calls

For the program above, identify which callable runs at each call of f(7.0) and what it returns.

  1. 1
    Step 1: first assignment

    f is assigned half. The variable now refers to the plain function half.

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

  3. 3
    Step 3: second assignment

    f is reassigned to a Doubler object. The old reference to half is replaced, and the call text is unchanged.

  4. 4
    Step 4: second call

    The Doubler object runs with 7.0. It computes 14.0 and returns 14.

  5. 5
    Step 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.

Result

The program prints 3, then 14, then 7.

Output
3
14
7

Three 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.

halfa named functionf(...)same call syntaxDoubler{}an object that behaves likea functionf(...)same call syntax[](double x) { ...}a small unnamed functionwritten in placef(...)same call syntaxinvoked through finvoked through finvoked through f
How do a plain function, a function object, and a lambda differ in how they are written, and how are they invoked through f?

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.

Debugging

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 like

    Reading f(7.0) and assuming it always runs half because half appeared earlier in the program.

    Why it fails

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

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

    A comment says f still holds the plain function, but a later line reassigns f to a function object.

    Why it fails

    A comment can be out of date. The assignment statements are what the program actually executes.

    Do this instead

    Trace 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

EASY

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.

Related concepts

More concepts starting with P