Over the years I have been programming I have not had any reason to use lambdas. I’ve seen them mentioned by other devs and have had to look up how to use them each time I’ve seen them. This left me with the feeling that they were somehow swathed in the forbidden and deep, dark magic of programming concepts (never you mind that I’ve written and used plenty of function pointers in my day). My understanding of them stayed this way until last week when I found the perfect combination of reason and supporting compiler to use a lambda. Given that I need to give a good explanation of lambdas to those on my team I thought I would just write and introduction to C++ lambdas and really solidify my knowledge. Teaching is, of course, the best way to highlight your misunderstanding of a topic (just as testing is the best way to highlight your misunderstanding of code you’ve written).
What Is A Lambda Expression?
A lambda expression, often called a lambda function or just a lambda, is an anonymous function. From my point of view as a long time C programmer: It is like (but not actually) a function pointer declared right where you need it and without all of the baggage. Baggage here is defined as a variable to hold the function pointer, pointer syntax, and a function definition off somewhere else not in the context of what you’re looking at right where you need it. While that may not seem like a lot it could certainly be a lot for a function that is only used once. (Of course this implies that this baggage is acceptable if the function will be used plenty of times across many areas of the code)
Another property of lambdas that is important to know is that, in addition to taking parameters like a normal function, it is able to “capture” the state of local variables in the calling function. In essence, instead of passing in a bunch of parameters to get the necessary state/context to do what you want to do you instead capture them directly from the caller. Then the only things that go in the parameter list are things like the current element of a container you’re working on with an algorithm such as sort or for_each.
Sidebar: A lambda is more like a function object than a function pointer
I mentioned earlier that a lambda is like a function pointer. That is true only in the sense that it can be used like a function pointer (much as a good old C array variable name can be used like a pointer but it isn’t really a pointer). In reality it is better to think of a lambda like a function object. The code that gets generated is like a class that overrides the () operator. This article explains what is going on under the hood. I plan to write my own post on the topic to help myself (and hopefully you) understand it better at some point soon.
What is The Purpose of Lambda Expressions?
With that rough definition of a lambda out of the way we can move on to what they’re good at. The short answer is: **short functions used in algorithms**. This includes things like custom comparators for sorting or operations to act on each item in a collection when going through them all. There are uses for lambdas beyond this, of course, given that they can be used in place of typical function pointers (this would be especially useful when it would be helpful to capture local state in addition to taking parameters). Some of these can be seen in the Microsoft docs here.
Let’s See An Example!
First An Easy C++ Lambda Example
Now that we have defined what a lambda is and what it can be used for, let’s take a look at an example. This follows the example given by CPPReference for std::for_each( ) closely because it shows several ways to use lambdas in one relatively simple example. I’ve written this example on Compiler Explorer so that you can see it run.
#include <vector>
#include <algorithm>
#include <iostream>
int main(void)
{
std::vector<int> vec = { 0, 1, 2, 3, 4, 5 };
auto print = [](const int& n) { std::cout << " " << n; };
std::cout << "Before Modification:";
std::for_each(vec.cbegin(), vec.cend(), print);
std::cout << '\n';
std::for_each(vec.begin(), vec.end(), [](int &n) {n += 2;});
std::cout << "After Modification:";
std::for_each(vec.cbegin(), vec.cend(), print);
std::cout << '\n';
return 0;
}
Code language: C++ (cpp)
Here we see two simple uses of lambdas; one assigned to a normal function pointer that prints a value with std::cout and one that adds two to an integer value. The commonality between both is that they only have to know about and act on a single variable which simplifies the logic we want to apply to each element in a container.
Let’s Use A Capture!
Let’s modify the example above to use a capture so that we can show what it is and how it works.
#include <vector>
#include <algorithm>
#include <iostream>
int main(void)
{
std::vector<int> vec = { 0, 1, 2, 3, 4, 5 };
auto print = [](const int& n) { std::cout << " " << n; };
std::cout << "Before Modification:";
std::for_each(vec.cbegin(), vec.cend(), print);
std::cout << '\n';
int valueToAdd = 2;
std::for_each(vec.begin(), vec.end(), [&valueToAdd](int &n) {n += valueToAdd;});
std::cout << "After Modification:";
std::for_each(vec.cbegin(), vec.cend(), print);
std::cout << '\n';
return 0;
}
Code language: C++ (cpp)
As you can see, the only thing that changed was the addition of valueToAdd and its use in the lambda. Because it’s not a parameter like the individual objects within vec we’ll put it in the captures list. You’ll also notice that its use in the capture brackets has the reference symbol (&). This indicates that we want it to be taken by reference instead of by value (i.e. copied). This isn’t a big deal in this example but it may if you’re capturing a large object or running lots of iterations. This example shows the use of a capture but doesn’t really explain the details of it. I’ll leave that for the next section (and links to blogs from people smarter than me).
Where to More Details on Lambdas
These examples show the basics of I ended up using a lambda for the first time in my code at work. It’s fairly simple and should get you started for a simple case. That being said I haven’t gone into a ton of detail on how the different parts of lambdas actually work (including how to handle the *this object, variadic parameters, exceptions, return types, etc). For more details on the specifics here are a few posts with a ton more details:
I have written more information on how lambda functions work under the hood in this post.