At a glance
Before reading, please read the part 1 of this series, otherwise some of the answers may not make sense.
Where part 1 explained what a lambda actually is under the hood, this will take more of an FAQ format and cover more in-depth questions. At the end of many questions, I will leave a link to further reading for the topic discussed.
- Lambdas are cheap
- They allocate on the stack, not heap
- Their assembly output is identical to using a class
- You don’t need to capture static variables
- If you want to return a lambda, use
std::function
or similar - Captured variables are immutable by default
- Copying a lambda will copy its state
- Capturing
this
is not a special case
- A quick recap on lambdas 🎓
- Key points for deep understanding of lambdas 🗝️
- Performance concerns 🚀
- What are the variable capture rules? 🕸️
- How to pass a lambda around 📧
- Other miscellaneous points 🌴
As we explored in part 1, a lambda is an expression, which when evaluated, creates a local functor class with operator() ready to call. It may also capture variables by passing them through the functor’s constructor.
A note on terminology:
Technically, a lambda is an expression that defines a functor class. A lambda is to a class, as a function is to an instance of a class.
For simplicity, I will use the term “lambda” to refer generally to the concept.
Key points 🗝️
The type of a lambda is determined at compile time. You’re essentially creating an anonymous class, and if you inspect your assembler output you’ll see it has a name as such:
lambda_4a776e7774c8d4ec8eddd924a4a3b251
As such, each lambda creates its own unique type.
Whether or not you capture variables, it’s the same assembler code as for a normal class. The only exception is that if capturing variables, the constructor is inlined as it won’t be re-used anywhere else.
Please read the following article for a deeper dive and proof of this:
Because the types are different. Consider writing two separate classes and assigning them to each other — it’s not valid C++.
Performance concerns 🚀
It’s cheap. (Depending on your definition of cheap!)
Memory wise: As a lambda will generate a class, it will be as expensive as creating an equivalent class that holds the same number of variables as you capture. Simply put, the more variables you capture (particularly by value), the larger your generated functor class, and the more expensive your use of a lambda will be. If you capture by reference generally, this will be no more than a few pointers.
Computationally: If you don’t capture any variables, it is literally a function call.You can’t really get any cheaper!
If you do capture a variable, the cost is the same as constructing an object and calling a function directly on it, with no virtual look-ups. (Also cheap.)
Importantly: The cost of a lambda is never greater than the cost of an equivalent function / class.
No. The functor instance will be created on the stack as if you constructed a class directly. However, putting std::function
on the left instead of auto
may heap allocate. See:
More on std::function
later.
Capturing variables 🕸️
The key thing to know here is that only automatic variables can be captured. This is any local variable to your function, including the this
pointer. These are pushed on and off the stack, and automatically destructed when you leave the current function.
No, this is a common misconception!
They will capture every variable that is used in the function definition, and no more. Your coding style may dictate you should explicitly name each captured variable however.
Yes, but only since C++14.
Nothing at all. this
is a pointer to the current object, and is captured explicitly or implicitly like any other variable. Remember that when writing class code, you can access member variables without writing this->
. This omission is the cause of some confusion with lambdas in classes.
In both of the following cases, you are capturing a pointer by value, and then dereferencing it.
You can in C++14 and higher. However, a lambda can only capture automatic variables, and a static variable, by definition, is not automatic.
You don’t need to capture static variables as they always have the same address in memory. It isn’t required — just use the static variable directly.
Note, some compilers may have static-capturing support before C++14 and warn as such, but the point still stands that you do not need to capture them.
You can, but recall that you can only capture automatic variables. This means you cannot explicitly capture member variables, because they’re actually behind a dereferenced pointer.
To access member variables, you need to capture this, whether explicitly or implicitly.
This is perfectly valid, use the following:
Passing lambdas around 📧
If you’re using C++14 and above, you can set a function’s return type to be auto
. This will let you use the functor type directly.
In C++11 and below, you cannot return auto
from a function, which poses a problem for the lambda type. The simplest way is to avoid this is convert it using std::function
, or some library alternative that can wrap the function nicely for you.
When using std::function
, watch out for potential heap allocations if you capture many variables, and the overhead of calling a function through a pointer rather than directly. For small or empty capture lists, the memory footprint of std::function
will be the same as the actual functor type due to a small object optimization.
Please read the following for more info how later versions of C++ will allow returning by auto:
And a comparison of an auto
lambda type vs one wrapped in std::function
:
It is possible to return a lambda by function pointer too, however the syntax is awful, and you will likely run into dangling allocation and lifetime issues. I strongly recommend against this unless you have a very specific use-case that either auto
or std::function
cannot fulfill.
You will copy the state as is, like copying a class object directly (assuming it has not overridden the copy constructor.)
This can be hard to visualise and catches out many people, so I encourage you run the following code and play around with it yourself:
Other miscellaneous points 🌴
Sure, knock yourself out. Lambdas can even return other lambdas if required:
Let’s take a look at the example generated code from part 1.
Note how on line 3, the operator()
declaration is const
, meaning it cannot mutate variables on the class.
If you need this to be non const, you can use the mutable
keyword, as so:
So far, we’ve just written a lambda and let the compiler figure out what type it will return, as if by magic.
Without explicitly specifying, lambdas will use “auto return type deduction rules”: essentially the same as if you put auto
to the left of a variable.
Yes, using alternative function syntax. Here, the return type is on the right hand side of the function rather than the left. You can use this in regular functions, member functions on classes, and lambdas too.
I’ve covered what I see as regular confusion points surrounding lambdas here. If you’ve learnt anything from this, or have further questions, please leave a comment or shoot me a message on Twitter @winwardo and I’ll update this guide :)