People who think that lambdas may cause heap allocations are confusing lambdas with std::function.
Lambdas are essentially syntactic sugar for an anonymous struct holding references to/copies of captured variables and an operator() method with your code. Not 100% precise, but memory-wise that's how it works (and has to work). No dynamic memory allocation is involved here at all.
If you put a lambda (or any other callable) in a std::function, the std::function will copy that functor object, either into its own small-buffer-optimized in-place storage or (above a certain functor size) into heap-allocated memory.
There is no dynamic allocation at all, unless you assign the lambda to an std::function, in which case it _may_ require dynamic allocation if the size of the captured variables exceed the internal storage capacity (typically 32 bytes) of std::function. There are types (usually called 'function_view') floating around that don't dynamically allocate at all, and that can be used as a cheap alternative when the function is not stored for later use.
Internally, lambdas are structs with the "function call" operator overload. Context is done via members of the struct: capture by-value, and the struct copies them and stores its own, capture by reference, and the struct member is a reference. There should be zero heap allocation in any case.
If I'm reading this right, then "lambda" in C++ isn't "lambda" in Scheme/CommonLisp, where captured variables need be heap allocated rather than stack allocated to construct closures?
You're right. Lambdas in C++ are syntactic sugar over something the core language has allowed you to do since before C++98. Since you control which variables you capture into the closure, and how, when writing the lambda, all of the information regarding how to make it (e.g. it's size, data members, etc) is static, and so it does not require the use of the heap at all. A concrete example. The following C++ examples do the same thing, and neither touch the heap:
// A. Using a struct. The old manual way.
struct my_lambda {
my_lambda(int c) : c_(c) {}
int operator() (int x) const {
return x == c_;
}
int c_;
};
auto f(vector<int> numbers) {
my_lambda lam(2);
// remove all the matching elements
erase_if(numbers, lam);
return numbers;
}
// B. Using a lambda
auto g(vector<int> numbers) {
int c = 2;
erase_if(numbers, [c](int x) { return x == c; });
return numbers;
}
It is complicated. In C++ closures can close over local variables either by value or by reference (the choice can be made for each variable closed over).
When closing over a variable by reference, if the lambda need to survive the local scope heap allocating the closure itself won't help. Instead you need to explicitly heap allocate the closed over variable itself (and close over the, usually smart, pointer).
When closing over by vale, there is no such issue, closed over variables are copied over along the lambda and it can be safely, for example, be returned from a function.
Copying might be expensive if the lambda is closing over an expensive to copy object, but move semantics are always an option.
Lambdas are value types, they are usually copied around. so when closing over ither va
That's dependent on the captured type's copy (or move) behavior, and is completely different from your claim that how the lambda is used can influence this. Lambdas themselves never heap allocate.
Internally, lambdas are function pointers + the context, and the context may or may not be dynamically allocated depending on how lambdas are used.