C++ Lambda Anonymous Functions - The Simple Guide
By Steve Claridge on 2022-12-14.
Here I will try and explain what lambda functions are and how you can use them in your C++ code.
So what are we talking about anyway?
There's a lot of different names for this: anonymous function, lambda function, first-class function, function literal, lambda expression or lambda abstraction.
I think anonymous function
makes the most sense to describe this as it is a function without a name. Let's
dive straight into an example so you will see how it doesn't have a name. From now on I'm going to use the term
anonymous function
throughout, instead of lambda or any other name.
When a function is a variable
First of all, here's a piece of code (with no lambdas) that simply defines an int variable a
and passes
it to function stuff()
, which prints it out. Bread and butter stuff, very simple:
void stuff(int num)
{
cout << num << endl;
}
int a = 10;
stuff(a);
The point on show here is that we've created a variable and passed it to a function.
One of the things you can do with anonymous functions is treat them like a variable, so instead of passing an
int to stuff()
we can pass a function to it!
void stuff(std::function<void()> func)
{
cout << func() << endl;
}
auto a = [](){cout << "hello" << endl;};
stuff(a);
Yeah, shit got real very quickly with the syntax, don't worry about it yet, all you need to know now is that we can create a function without a name and pass it around like it's an int or any other type of variable.
Already we've got problems
Normally when you create a function in C++, it has a defined scope
. For example, if you create a class
and add some public/private functions then those functions are scoped to that class. If you create a function
in a file called stuff.cpp then it is scoped to that cpp file.
It's a bit more complicated with anonymous functions because they can move around, so their scope changes. We
saw in the example about that I created an anonymous function, assigned it to variable a
and then passed
it to another function called stuff()
, where it was executed. That means, when I created the function, it
was scoped differently to when I ran it. Lets see another example to explain this:
void stuff(std::function<void()> func)
{
func();
}
string name = "Bobby Dazzler";
auto a = [](){cout << name << endl;};
stuff(a);
Again, don't worry about syntax yet, just working through some concepts about scope. So, now we have a new
name
variable and our anonymous function [](){cout << name << endl;}
prints it to standard out. Don't forget
though that the anonymous function is passed to stuff()
and then executed there.
Notice that at the point that I define the anonymous function, the variable name
is available, but what
about when we execute the anonymous function inside of stuff()? How is name available then? Clue: it's not.
About that syntax
Lets break down some of that funky-looking syntax, here's a definition of an anonymous function:
[](){}
Yeah, I know. Here's a traditional function:
int more_stuff(int a, int b)
{
//stuff happens here
}
Our more_stuff
function has a return type of int and accepts two parameters. Our
anonymous function needs some parameters too, lets specify two ints again:
[](int a, int b){}
So the ()
part of the definition is the same as in a traditional function definition, it contains
the parameters. The {}
part of the definition is also the same as before, it holds the code, we can, for example
add them together:
[](int a, int b){return a + b;}
What about the return type?
I'm glad you asked.
The C++ compiler can automatically deduce the return type from the code, in the above example the return type of the anonymous function is implicitly an int. Similarly, if we had this function then the return type would automatically be a string:
[](string a, string b){return a + b;}
You can specify the return type if you want to, it adds some more funk onto that already funky syntax. :
[](int a, int b) -> {}
Using the same example as above, but this time we want to return a long in case the two input ints are too large to return as an int:
[](int a, int b) -> long {return a + b;}
And the [] ?
Think back to the section at the start about scoping variables. As I said above, an anonymous function can be passed around like a variable, which means that it could be executed in any function it is passed to. This means that you can't safely assume what variables are available (in scope) when the function is executed. Lets see an example where this will produce an error:
void run_it(std::function<int()> fnc) {
std::cout << fnc() << std::endl;
}
int main () {
int c = 7;
auto a = []() -> int {return (10 * 4) * c;};
run_it(a);
}
if you compile this, g++ will complain with variable 'c' cannot be implicitly captured in a lambda
. The anonymous
function tries to use the variable c
, this might look to be OK because c
is declared directly above the
anonmyous function declaration. The important thing to remember here is that auto a = []() -> int {return (10 * 4) * c;};
is declaring a function that will be run later, not actually running the function. So, yeah c
is in scope at
the time of declaration, but it won't be when the function is run inside of run_it
. The compile realises this
and complains, so how do we fix it?
the []
part of the anonymous function definition is the capture
part, it's where you can specify variables
that you want to remain in scope when the function is run but they aren't actual parameters of the function. Lets
change the above code by adding c
as a captured variable:
int c = 7;
auto a = [c]() -> int {return (10 * 4) * c;};
}
This will now compile, you can put pretty much anything inside the capture, if your function is being defined within a class you can capture this for example.
About std::function
We've been creating functions that accept a function as a parameter, e.g.:
void run_it(std::function<void()> func)
void run_it2(std::function<int(string, string)> func)
The syntax here simply defines the return type and any parameters of a function that it will accept within <>
.
So std::function<void()>
returns a void and has no parameters. function<string(int, char)>
returns a string
and has an int and char parameter.
A working example
#include <iostream>
#include <functional>
void run_it(std::function<int(int, int)> fnc) {
std::cout << fnc(4, 3) << std::endl;
}
int main () {
int c = 4;
auto a_func = [c](int a, int b) -> int {return (a * b) * c;};
run_it(a_func);
c = 2;
auto another_func = [c](int a, int b) -> int {return ((a - b) * 100) / c;};
run_it(another_func);
}
run_it accepts a function that returns an int and has two int parameters. Two anonymous functions are
created a_func
and another_func
, both capture c
, if you compile and run this it will print out
48
50
You can compile this with g++ -std=c++11 test.cpp
Some things to try
A few things to try to really nail your understanding:
- Create a run_it2 function that accepts a function that has two string parameters and call it with two newly defined anonymous functions.
- Move the run_it calls to the bottom of main and observe if the value of
c
changes in each call. - Create run_it3 that accepts a function that has no parameters and returns a void. Call it with an anonymous func.