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.