Concepts / Function Objects

Function Objects

A lambda expression is a notation that generates a function object, similar in kind to writing Less_than<int>{x}.

  • Programming
Interactive lab

Try it: Functions as Values: Lambda

That a lambda (like def) evaluates to a function object that a name can refer to and that can be passed to other functions — apply(f, x), twice(f, x), map(f, xs), sorted(xs, key=f) — and that each call is traced by binding the argument, substituting it into the single expression and evaluating in operator order.

How it works

  1. lambda x: expression evaluates to a function object (named <lambda>); nothing inside it is computed yet. f = ... gives it a name, exactly as def f(x): return expression would.
  2. Writing f without parentheses passes the function object itself; inside apply or twice the parameter func refers to the same object.
  3. A call binds the argument to the parameter x, substitutes it into the expression, and evaluates by operator precedence (* and % before + and -, parentheses first).
  4. The value of the expression is the call's return value; there is no other body.
  5. map(f, xs) calls f once per item and keeps the results in order; sorted(xs, key=f) calls f once per item to get keys, orders by the keys (ties keep their order) and returns the items.

Default run (9 steps): The program is ready: a lambda written inline, then f(x). … print shows 7.

Simplified: Four one-parameter lambdas with editable integer constants, small int arguments (at most 6 items). Each substituted expression the lab shows was evaluated by CPython 3.12 and has the same value as the real call; printed results and ZeroDivisionError messages match CPython.

Educational simulation

Loading the simulation…

A Small Function Written Where It Is Needed

Many useful functions are small and short-lived. You write one to hand to an algorithm, to filter a collection, or to sort some items, and then you never need it again. Writing a full named function for each of those jobs is heavy. A lambda expression lets you write the function right where it is needed.

The harder question is what such a small function is allowed to see. Its body may refer to names that were declared in the surrounding code. The capture list is the part of the lambda that answers how those outside names are reached. Keep one question in mind as you read: when the lambda's body mentions a local name from the place where the lambda was written, does the function work with that name itself, or with a separate value it has been given?

What a Lambda Expression Produces

Definition

A lambda expression is a notation that generates a function object. The source describes the object it creates as the same kind of thing you would get from writing Less_than<int>{x}, which is a function object constructed with x as its stored value.

So a lambda is not a special kind of call. It is a compact way of producing an object that can be called with an int and that returns the result of a comparison. Consider the notation [&](int a){ return a<x; }. The name less_than_x can hold the function object that this lambda generated. Calling it with 5 runs the body a < x using the parameter a. The part between the first pair of square brackets is the capture list, and it is what lets the body reach x, which is declared outside the lambda.

Lambda expressionwritten in the codeFunction objectgenerated, then calledCaptured xstored inside the objectCall with 5runs a < xgeneratesholdsis called
What the lambda notation produces, and how a captured name becomes a stored part of that object

Read the diagram from top to bottom. The lambda expression is the notation you write. The compiler-generated function object is what that notation produces, and it is what the name less_than_x ends up holding. Each name the capture list brings in becomes something the object can use, which is why the capture list decides the behavior of the object as a whole.

Tracing a Lambda Call Step by Step

cpp
Notes on the code
  • x is a local variable declared in the surrounding code
  • the lambda captures x by reference through the bare ampersand
  • the object is stored in less_than_x and can be called like a function
  • 5 is passed as the parameter a, so the body evaluates 5 < 10
Worked example

Following one call to less_than_x

Given the code above, what is the value of result, and which value of x does the body use?

  1. 1
    Create the object

    The lambda expression generates a function object and the name less_than_x holds it.

  2. 2
    Call the object

    less_than_x(5) starts running the body with the parameter a set to 5.

  3. 3
    Find the name x

    The body refers to x. The capture list [&] tells us x is accessed by reference, so the body works with the original local x.

  4. 4
    Evaluate the comparison

    The body computes a < x, which is 5 < 10.

Result

result is true, because 5 is less than 10.

What do you think happens?

If the code runs with x set to 10 and the lambda captured x by reference, what does less_than_x(5) return?

Reveal answer
Answer:

true

The body evaluates 5 < 10, which is true. The result depends on the value of x that the body sees at the time of the call.

Reading the Capture List

A capture list always sits first, inside square brackets, before the parameter list. The rule it encodes is simple once you see the pattern. A bare ampersand or equals sign applies to every local name the body uses. An ampersand or equals sign followed by a name applies only to that one name. The ampersand means the body works through a reference to the original. The equals sign means the generated object receives a copy of the value. Empty brackets mean the body is given no local names at all.

[&x]capture list(int a)parameter list{ return a < x; }body
Each part of the lambda notation and what it controls
  • [&] : every local name the body uses is accessed by reference
  • [=] : every local name the body uses is copied into the generated object
  • [&x] : only x is accessed by reference
  • [=x] : only x is copied into the generated object
  • [ ] : the body is given no local names from outside

Reference Captures Versus Value Captures

Suppose a program has a local variable x and wants two function objects that compare an incoming int against it. One lambda should refer to x directly, and the other should carry its own copy. The two lambdas look nearly identical, so the capture list is the only place where their behavior is decided. A reference capture always lets the body see the current state of the original name. A value capture lets the object keep its own copy, separate from the original.

cpp
Notes on the code
  • both lambdas have the same body and the same parameter list
  • only the capture list differs: [&] for by_reference and [=] for by_value
  • x is changed to 3 after both objects are created
  • by_value was given its own copy of x when it was created
x (original local)value 3 after x = 3by_reference objectcaptured by [&]stored copy of xvalue 10by_value objectcaptured by [=]reads current valuereads its own copy
Whether the body reads the original local x or a separate value stored inside the function object

What do you think happens?

After x is changed to 3, what are r1 and r2?

Reveal answer
Answer:

r1 is false and r2 is true

by_reference reads the current x, which is 3, so 5 < 3 is false. by_value keeps its own copy made when it was created, which is 10, so 5 < 10 is true.

Choosing a Capture Style

The four styles answer different questions. Ask first whether the body needs any local names at all. If it does not, capture nothing. If it does, decide whether the body should see the original names and their current values, or work with copies the object keeps. Use a single named capture when only one local is needed and the rest should stay out of the body.

Capture listWhat it applies toHow the body reaches the nameWhen to use it
[ ]No local namesThe body cannot use outside localsThe body needs only its parameters
[&]Every local name the body usesBy reference to the originalMany outside names are needed and current values matter
[=]Every local name the body usesBy a copy stored in the objectMany outside names are needed and a snapshot is wanted
[&x]Only the named local xBy reference to the original xExactly one outside name is needed and its current value matters
[=x]Only the named local xBy a copy of x stored in the objectExactly one outside name is needed and a snapshot of it is wanted

The four capture choices in the source, with the single named capture forms included

EASY

Given int limit = 7; and a lambda that should report whether its parameter is greater than limit, which capture list would you write if limit must always reflect its current value, and which if the object should keep the value it had when created?

Hints
  • Ask whether the body must see the original limit or a separate copy.
  • A reference capture always reads the current state of the original name.
  • A value capture keeps its own copy, made when the object was created.

Mistakes With Capture Lists

  • Common mistake

    Assuming a lambda with [=] shares the original local

    What it looks like
    int x = 10; auto f = [=](int a) { return a < x; }; x = 3; f(5) still compares against 10.
    Why it fails

    A value capture gives the generated object its own copy, separate from the original name.

    Do this instead

    Use [&] or [&x] when the body must see later changes to the original local.

  • Common mistake

    Expecting [ ] to make an outside name visible

    What it looks like
    int x = 10; auto f = [](int a) { return a < x; };
    Why it fails

    Empty brackets mean the body is given no local names at all, so x is not accessible inside the body.

    Do this instead

    Write a capture list that names x, such as [&x] or [=x].

  • Common mistake

    Treating the capture list as decoration

    Why it fails

    Two lambdas with identical bodies can behave differently only because their capture lists differ.

    Do this instead

    Read the capture list first and ask, for each name the body uses, whether it is accessed by reference or by value.

  • Common mistake

    Forgetting that a bare ampersand or equals sign applies to every local name the body uses, not just the first one

Practice Reading Capture Lists

MEDIUM

For each lambda below, state whether each named local the body uses is accessed by reference or by value: (a) [&] with a body using x and y; (b) [=x] with a body using x and y, where y is not captured by name; (c) [ ] with a body that uses only its parameter.

Hints
  • A bare ampersand or equals sign applies to every local name the body uses.
  • A named capture applies only to the name written after the sign.
  • Empty brackets mean no local names are given to the body.

Key Takeaways

  • A lambda expression is a notation that generates a function object, similar in kind to writing Less_than<int>{x}.
  • The capture list sits first in square brackets and decides how each local name used in the body is reached.
  • An ampersand means the body works through a reference to the original name, while an equals sign means the object receives a copy of the value.
  • A bare sign applies to every local name the body uses, a sign followed by a name applies only to that name, and empty brackets give the body no local names.
  • Choose the capture style by asking whether the body needs outside names at all, and whether it should see their current values or a snapshot.

Related concepts

More concepts starting with F