Using Lambda Functions for Delayed Evaluation

Lambda functions (sometimes called “anonymous functions”) have been a part of C++ for many years now, so usage of them should be well accepted. This mini-article is intended to outline a use case for using lambdas to perform a type of “lazy evaluation”, a term from FP (functional programming) languages. Lazy evaluation is not in fact very different from conventional evaluation, except in that the evaluation is delayed until the result is actually required. We will look at how this can be useful when working with (mutable) independent variables which are able to be changed (something usually not possible with FP).

From school Math class you probably remember writing equations such as y = 2x + 1, and then calculating y based upon varying values of x. Simple stuff, which translates easily into C++:

int x = 10;
int y = x * 2 + 1;  // y has value 21

What happens when x changes? Of course, we have to remember to recalculate and re-assign to y:

x = 9;
y = x * 2 + 1;  // y how has value 19

But what if we wrote y as a lambda function (strictly speaking assigning an anonymous function to variable y)?

int x;
auto y = [&]{ return x * 2 + 1; };

Suddenly, y is not a mere variable, it’s a callable object, and by virtue of the fact it “captures” x (due to use of [&]), any future changes in x will be reflected in the result of calling y() (the empty parentheses are necessary to invoke it):

x = 10;
std::cout << "x = " << x << ", y = " << y() << '\n';  // "x = 10, y = 21"
x = 9;
std::cout << "x = " << x << ", y = " << y() << '\n';  // "x = 9, y = 19"

The way to use this game changer is very simple:

  1. Decide on the independent variable(s) needed
  2. Write the formula for calculating the dependent variable as a return statement in a lambda
  3. Capture the independent variable(s) as necessary by reference ([&])
  4. Call the lambda every time the independent variable changes to obtain the current value of the dependent variable.

That’s really all there is to it. A program to show usage of this technique is shown below:

#include <iostream>
#include <array>

using namespace std;

int main() {
    double x;
    auto y = [&x]{ return x / 2 + 5; };
    auto y2 = [&x]{ return x * x / 100; };

    constexpr int sizeX = 79, sizeY = 24, originX = 39, originY = 12;
    constexpr int minX = -39, maxX = 39, minY = -12, maxY = 11;
    array<array<char,sizeX>,sizeY> graph;
    for (auto& y : graph) {
        for (auto& x : y) {
            x = ' ';
        }
    }

    auto plot = [&](auto f) {
        for (x = minX; x <= maxX; ++x) {
             auto y = f();
             if (y >= minY && y <= maxY) {
                 graph[sizeY - 1 - (y + originY)][x + originX] = '#';
             }
        }
    };

    plot(y);
    plot(y2);

    for (auto& y : graph) {
        for (auto& x : y) {
            cout << x;
        }
        cout << '\n';
    }

}

The important parts are listed at the beginning, lines 7-9 assign the formulas and the independent variable to be used. The code at lines 11-18 creates a two-dimensional array from assigned constexpr dimensions and fills it with space characters. The lambda function plot at lines 20-27 accepts another lambda and calls it to obtain values of y based upon x, filling in a cell in the array if it is within the array bounds. (The array is stored upside-down so needs to have the y index negated.) Finally lines 29-37 call the plot lambda with the lambda functions and print out the graph.

That’s everything for this article. We’ve looked at how lambda functions can be used for delayed evaluation to replicate use of formulas from Math, and how lambda captures can be used to update independent variables used by the formula. See if the output from this program matches your expectations by compiling and running it; find the source code on GitHub.

Leave a comment