Lambdas

Cpp

#include <algorithm>
#include <functional>
#include <vector>

int main() {
    std::function<int(int)> addOne = [](int x) -> int {
        return x + 1;
   };

   addOne(5);
   // returns 6

    // use lambda as value, do in-place modification
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::transform(v.begin(), v.end(), v.begin(),
        addOne);
    // new value of 'v' is [2, 3, 4, 5, 6]
}

JavaScript

const addOne = (x) => {
    return x + 1;
}

addOne(5);
// returns 6

// use lambda as a value
[1, 2, 3, 4, 5].map(addOne)
// returns [2, 3, 4, 5, 6]

What This Code Does

The above code creates a simple lambda that adds 1 to the value provided. It is called directly and also passed as a value into functions that operate over lists of data.

What's the Same

The JavaScript and the C++ version define a lambda that is both callable directly and can be passed as a value without any special treatment. Both versions also define "pure" lambdas that do not capture any local state or perform any kind of mutating state-changes (i.e. calling the code, alone, does not affect our program).

What's Different

The differences mostly lie in type declarations and syntax. While the structure is the same, C++ must define all of the parameter and return types. This is most visible in how our lambda is structured.

std::function is used to represent the return and parameter types like so:

std::function<ReturnType(Arg1Type, Arg2Type, Arg3Type)>

The lambda itself has 4 parts. The capture, the parameters, the return type, and the body.

auto lambda = [](int param1, int param2) -> int {
    return param1 + param2;
}

The [] part is a list of variables we'd like to "capture" in our lambda (more on this in the next section). The parameters are everything between ( and ) and what comes after the ->, but before the {, is the return type. And what's in the curly-braces is the function body. It's also worth pointing out that we must always use return if we want to return a value from our lambda. There is no short-hand equivalent of JavaScript's curly-brace-less lambda ():

const addOne = (x) => x + 1;

Capturing Local State

Cpp

#include <functional>

template<typename T>
std::function<T(T)> addX(T x) {
    return [x](T n) -> T {
        return n + x;
    };
}

int main() {
    auto addFive = addX<int>(5);

    addFive(10);
    // returns 15
}

JavaScript

function addX(x) {
    return (n) => {
        return n + x;
    }
}

const addFive = addX(5);

addFive(10);
// returns 15

What This Code Does

addX is a function that returns another function. The function it returns adds x to whatever value is provided. x is initially given to the addX function but is captured by the lambda that is returned.

What's Different

The first notable difference is the use of template <typename T> written before the addX function. C++ must make use of "generics" in order to account for different numeric types, such as int, float, double, etc. This is done with what is called C++ templates. Templates are expanded at compile-time into concrete implementations. So, to be fair, this is less generic programming and more akin to sophisticated macros.

// When used in code, a version is compiled to match the usage.
template<typename T>
sum(T a, T b) {
    return a + b;
}

void main() {
    sum<int>(1, 2);
    sum<double>(1.0, 2.0);
    // this causes 2 specialized versions to be compiled
}

In Javascript, all checking is done at runtime. That is to say, if we were to sum or multiply two types that were not compatible (e.g 4 * {some: "object"})), we would not know until the program ran. Many would point this out as a drawback to writing scalable, maintainable code and is reflected with the popularity of such projects as TypeScript which attempts to add types to JavaScript. With C++, we will know at compile time when the templates are expanded into concrete functions. At that point we will know if the types can be added, multiplied, etc. Of course, most modern IDEs will give you some advanced notice as well. :-)

The second notable difference is that the capture portion of our lambda ([]) contains the variable x. This value is copied into the lambda. Since C++ is not a garbage-collected language, we have to be specific about which variables we want to capture and how we'd like to capture them. For things we may not want to copy, we can pass by reference.

int main() {
    auto bigString = "I'm a really long string, don't copy me ...";
    auto printFn = [&bigString]() -> void {
        std::cout << bigString;
    };
    printFn();
}

Note the & before the variable name in [&bigString]. This copies the reference to the variable instead of copying the value into a new variable. The caveat with this is that a reference is just a pointer to a location in memory. If the variable is cleaned up before the lambda is called, bad things could happen.

Digging Deeper

Everything in C++ must have some sort of type and representation in memory, and the same is just as true for lambdas as it is any other type. Take the following:

int main() {
    auto age = 65;
    std::string name = "Sir Robert Christianson Manyard Sr";

    auto printPerson = [age, &name](bool includeAge) -> void {
        std::cout << name;
        if (includeAge) {
            std::cout << ", age: " << age;
        }
        std::cout << "\n";
    }
}

The specification for C++ says that a lambda is an object of an anonymous type, created on the stack. You can imagine the equivalent for this would look like:

struct LambdaPrintPerson {
    int age;
    std::string *name;

    void printPerson(bool includeAge) {
        std::cout << name;
        if (includeAge) {
            std::cout << ", age: " << age;
        }
        std::cout << "\n";
    }
};
int main() {
    auto age = 65;
    std::string name = "Sir Robert Christianson Manyard Sr";

    auto printPerson = LambdaPrintPerson { age, &name };
}

The reason you can imagine a lambda to look like this is because the exact layout (padding, alignment, etc) is compiler dependent according to the specification. However, it should make more sense now how values are "captured" by the lambda and the difference between capturing a reference (no copy) and capturing a value (copy).

Note that it is possible to allocate a lambda on the heap, even though the default is to allocate on the stack. Since a lambda is just an object, it can be heap allocated with the new keyword, such as:

auto printPerson = new auto ([age, &name](bool includeAge) -> void {
    // function body
});

Note the only additional bit of syntax is that we must wrap the lambda in parens ().

Consuming

Cpp

#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>

void sendEmail(std::string to, std::string from, std::string subject,
               std::string body,
               std::function<void(std::string)> success_cb,
               std::function<void(std::string)> failure_cb) {
    if (std::rand() > 0.5) {
        success_cb(to);
    } else {
        failure_cb(to);
    }
}

int main() {
    sendEmail("you@your_domain.com", "me@my_domain.com",
        "Very Important Email",
        "TODO: remember to write email body. :-D",
        [](std::string to) -> void {
            std::cout << "Successful email sent to: " << to << "\n";
        },
        [](std::string to) -> void {
            std::cout << "OH NO! Very important email not sent to "
                      << to
                      << "\n";
        });
}

JavaScript

function sendEmail(to, from, subject, message,
        success_cb, failure_cb) {
    // pretend we tried to send an email and call the
    // success or failure callback randomly.
    if (Math.random() > 0.5) {
        success_cb(to);
    } else {
        failure_cb(to);
    }
}

sendEmail('you@your_domain.com', 'me@my_domain.com', 
    'Very Important Email',
    'TODO: remember to write email body. :-D ',
    (to) => { 
        console.log('successful email sent to: ' + to);
    },
    (to) => {
        console.log('OH NO! Very important email not sent to ' + to);
    });

What This Code Does

sendEmail is a user-defined function that sends and email and then calls one of two callbacks provided by the user. This shows how to receive (consume) lambdas in your own code.

What's Different

Consuming lambdas in user-defined functions is very straight forward. The main difference here, yet again, is that the C++ version has to do slightly more work in order to define all of the types. Beyond the type declarations, the two versions are very similar.

Fork me on GitHub