The more I read about lambdas the more I hear from people that they are just function objects/functors in disguise (unless they don't capture anything, in which case they're just free static functions. I want to write lambdas at the local scope and pass them to a universal event handler, which calls them as needed, and I'm starting to notice that I can hardly do anything that a traditional function object lets me do. Please let me know if my understanding of this is wrong, as I've commented a whole bunch of stuff you can do with functors and can't with lambdas, as far as I know:
#include <iostream>
#include <vector>
struct MyFunctorClass
{
// Custom constructor, can't do with lambda
MyFunctorClass(int& capturedVariable) : capturedVariable(capturedVariable)
{ std::cout << "I can do anything on construction.\n"; }
// Overloading constructors, different ways to initialise function object, can't do with lambda
MyFunctorClass(int& capturedVariable, int sizeOfBuffer) : capturedVariable(capturedVariable)
{ heapAllocation = new int[sizeOfBuffer]; }
// Custom destructor, can't do with lambda
~MyFunctorClass() { delete[] heapAllocation; }
void operator()() { std::cout << "Standard call\n"; }
void operator()(int arg) { std::cout << "Argument passed: " << arg << '\n'; }
// operator() overloading, different ways to call the function object, can't do with lambda
int* heapAllocation; // Have heap allocated resources, can't do with lambda
bool internalStateVariable = true; // Initialise a member variable on construction, can't do with lambda
int& capturedVariable; // I can access this variable directly with MyFunctorClass::capturedVariable = 7, can't do with lambda
};
int main()
{
int localVar = 0;
bool trueOrFalse = false;
{
MyFunctorClass* myFunctionObj = new MyFunctorClass(localVar, 100);
// Can dynamically allocate function object, can't with lambda
auto lambda = new[&]() { localVar = 1; }; // Can't do?
lambda.trueOrFalse = true; // trueOrFalse isn't member of lambda, even though it captured it, doesn't make sense
} // Lambda object is destroyed here. My function object lives until I delete it.
return 0;
}
void holdFunctionObject(MyFunctorClass* funcObj)
{
static std::vector<MyFunctorClass*> list;
list.push_back(funcObj);
// I can hold all the function objects forever, they'll never go out of scope unless I delete them, can't do with lambda
}
I feel really restricted, it seems like lambdas are just a way of declaring functions "in place". They also hold state, but only can hold state of objects that are already in scope, not create new ones. And also can't be initialised in any specific ways that functors can. Have I got this right? Because they seem VERY different from just a class with overloaded operator();
No, you can not overload the lambda! Which is not possible, as the same variable name can not be reused in C++.
A lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. A lambda begins with the capture clause. It specifies which variables are captured, and whether the capture is by value or by reference.
The Lambda service stores your function code in an internal S3 bucket that's private to your account. Each AWS account is allocated 75 GB of storage in each Region. Code storage includes the total storage used by both Lambda functions and layers.
It is a convenient way to define an anonymous function object or functor. It is convenient because we can define it locally where we want to call it or pass it to a function as an argument. Lambda is easy to read too because we can keep everything in the same place.
I don't get what's your point; yes, lambdas are just syntactic sugar for instances of classes with operator()
that follow some specific template1; yes, you cannot do most of the stuff you can do with fully custom classes - and for a good reason: if you want to do that stuff, you can already write a regular class
.
Lambdas take a particular, widely employed pattern (throwaway functors that may capture state)2 and gives them extremely convenient syntax3. If you need fully fledged classes, the language already have you covered since the mid 70s with the class
keyword.
Also, it's important to note that, although lambdas in C++ are implemented in terms of classes, the "view of the world" that underlies the two concepts is different; closures are rooted in functional programming, not in OOP. The idea behind them is to model a single function (=action) along with the data it needs to work that was captured at creation site; an object instead is first and foremost a mutable entity which provides an interface to mutate or query its state. The fact that closures can be implemented in terms of objects and objects can be implemented in terms of closures is interesting and intriguing (and ultimately comes from the fact that both are a combination of state and code that act on it), but they start from quite different grounds. Using one or the other is essentially a matter of understanding whether what you want is primarily code that happens to capture state (a "verb") or is mostly "packaged state" with an interface to mutate it (a "noun").
unless they don't capture anything, in which case they're just free static functions
This is factually incorrect. Capture-less lambdas can be converted into function pointers, but they're not the same thing as a "free static function". They're still objects, while static functions are not (though you can have pointers to functions, that doesn't make functions be objects in the C++ sense of the term).
As for the rest, you seem to not recognize the difference between "all lambdas are functors" and "all functors are lambdas". The former is true, the latter is not, nor is the latter intended to be true.
Lambdas hold their state privately. Hand-written functors hold their state however you want. Most people would consider a functor with publicly accessible state to be bad form, but if that's what you want, that's a decision that's up to you.
By using a lambda, you agree to accept its limitations. The type of a lambda is synthesized at the point of the declaration, and it is not named, so you can't create APIs that accept a specific lambda (well, maybe you can, but you shouldn't). If you want the user to pass a specific functor type, then you should not be using a lambda.
But really... how often do you need the user to pass a specific functor type? Most of the time, what you need is for the user to pass something that can be called with a specific signature. That's what std::function
is for. Alternatively, if it is a template function, you can take an arbitrary object that is callable with a specific signature.
And if you really do need the user to pass a specific functor type... so be it. But since most interfaces will not be so restricted, a lambda is an effective solution.
Lambdas are, at their core, syntactic sugar that makes it easy to write common functors. There will certainly be occasions where a lambda just cannot do what you need. But since lambdas cover about 90% of uses for such things, that makes them good enough.
The main purpose of lambdas is to logically put the code for some process adjacent to the place where you're going to invoke that process. This provides locality of the code, relative to what that process is. This is common for callbacks, continuations, and the like.
It also allows such code to access the local scope of the callback easily, compared to a hand-written functor. This makes such processes feel like part of the code, rather than having to track down the definition of the functor type and read its operator()
implementation to figure out what's going on.
Also:
auto lambda = new[&]() { localVar = 1; }; // Can't do?
You can do this:
auto lambda = new auto([&]() { localVar = 1; }); // Can't do?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With