When to Use Functors Over Lambdas

When to use functors over lambdas

A lambda is a functor - just defined with a shorter syntax.

The problem is that this syntax is limited. It doesn't always allow you to solve a problem in the most efficient and flexible way - or at all. Until C++14, the operator() couldn't even be a template.

Furthermore, a lambda has exactly one operator(). You can't provide several overloads to distinguish between, say, the types of the arguments:

struct MyComparator
{
bool operator()( int a, int b ) const {return a < b;}
bool operator()( float a, float b ) const {return /*Some maths here*/;}
};

.. or value category of the object argument (that is, the closure object that is called). You can also not define special member functions, including constructors and destructors - what if a functor shall be responsible for resources?

Another problem with lambdas is that they cannot be recursive. Of course, normal functions (including operator functions) can be.

Also consider that lambdas are unhandy to use as comparators for associative containers or deleters for smart pointers: You can't directly pass the closure type as a template argument and need to construct the containers member from another closure object. (Closure types don't have a default constructor!). For a block-scope map that isn't too much of a hassle:

auto l = [val] (int a, int b) {return val*a < b;};
std::map<int, int, decltype(l)> map(l);

Now, what happens if your map is a data member? What template argument, what initializer in the constructors initialization list? You'd have to use another static data member - but since you have to define it outside the classes definition that is arguably ugly.

To sum up: Lambdas aren't useful for more complex scenarios because they weren't made for them. They provide a short and concise way of creating simple function objects for correspondingly simple situations.

Lambda Expression vs Functor in C++

1) It's trivial and trying to share it is more work than benefit.

2) Defining a functor simply adds complexity (due to having to make a bunch of member variables and crap).

If neither of those things is true then maybe you should think about defining a functor.

Edit: it seems to be that you need an example of when it would be nice to use a lambda over a functor. Here you go:

typedef std::vector< std::pair<int,std::string> > whatsit_t;

int find_it(std::string value, whatsit_t const& stuff)
{
auto fit = std::find_if(stuff.begin(), stuff.end(), [value](whatsit_t::value_type const& vt) -> bool { return vt.second == value; });

if (fit == stuff.end()) throw std::wtf_error();

return fit->first;
}

Without lambdas you'd have to use something that similarly constructs a functor on the spot or write an externally linkable functor object for something that's annoyingly trivial.

BTW, I think maybe wtf_error is an extension.

When to not use lambdas over normal functions?

When not to use lambdas:

  1. When the function is declared in a header and implemented in a .cpp files. Can't do this with lambda.

  2. When the function is a template, and you want to be able to manually specify template arguments. Doing this with lambas requires an ugly syntax: foo.operator()<...>(...).

When to use lambdas:

  1. When the function is overloaded/templated, and you want to conveniently pass it as an argument to a different function.

  2. You want to avoid ADL.


Other than that, preferring regular functions over lambdas is just a convention.

If you want to go against this convention, you should be prepared to explain your reasoning.

Using functors with STL algorithms versus lambda expressions?

Yes you're right.

A lambda expression basically creates a nameless functor. It just allows the programmer to do it in less code. Before C++11 the standard algorithms could only work with functors which would require the programmer to set up a whole new class just for some specific behaviour (like you just did).

This is exactly the reason why lambda's were introduced into C++11, to make sure using the standard algorithms with a custom functor weren't such a pain to write anymore.

Function pointer, Functor or Lambda?

Lambdas and functors are one of the most advanced C++ topics. You're better off starting with some fundamentals before, and have a solid understanding of how C++ classes work.

But, since you asked, the C++ equivalent of this should be something like this:

class Slider
{
public:
void onMouseDown()
{
if(rightClick or ctlKeyDown)
{
if(myFunctionPointer)
{
// show popup menu
myFunctionPointer(this);
}
}
}

std::function<void (Slider *)> myFunctionPointer=nullptr;
};

class Editor
{
public:
void showPopupMenu(Slider *s)
{
// build the popupMenu with information based on the slider
}

void init()
{
// create a slider and connect the popupMenu function to it
mySlider = new Slider;

mySlider->functionPointer = [this](Slider *)
{
showPopupMenu();
};

}

Slider *mySlider;
};

As I said, I think that you're better off focusing your efforts on getting the fundamentals down pat, first, before plunging into these shark-infested waters.

And just to add some additional color: this will compile (the only thing that's missing is the definitions of rightClick or ctlKeyDown), but it may or may not be right, depending on the scope and the lifetime of the objects involved. It may or may not be necessary to have the lambda capture a std::shared_ptr, instead of this, depending on how the objects in this application get instantiated. Understanding how C++ objects work would be a necessary prerequisite before dealing with closures and callbacks, of this sort.

Speed of bound lambda (via std::function) vs operator() of functor struct

The difference between the two cases is fundamentally that with the functor, the compiler knows exactly what will be called at compile time, so the function call can be inlined. Lambdas, interestingly enough, also have a unique type. This means again, when you use a lambda, at compile type (since the compiler must know all types) the function being called is already known, so inlining can occur. On the other hand, a function pointer is type based only on its signature. The signature must be known so that it can be called to and returned from appropriately, but other than that a function pointer can point to anything at run-time, as far as the compiler is concerned. The same is true about std::function.

When you wrap the lambda in a std::function, you erase the type of the lambda from a compiler perspective. If this sounds weird/impossible, think of it this way: since a std::function of a fixed type can wrap any callable with the same signature, the compiler has no way of knowing that some other instruction won't come alone and change what the std::function is wrapping.

This link, http://goo.gl/60QFjH, shows what I mean (by the way, the godbolt page is very very handy, I suggest getting acquainted with it). I wrote three examples here similar to yours. The first uses std::function wrapping a lambda, the second a functor, the third a naked lambda (unwrapped), using decltype. You can look at the assembly on the right and see that both of the latter two get inlined, but not the first.

My guess is that you can use lambdas to do exactly the same thing. Instead of bind, you can just do value based capture with the lambdas of a and b. Each time you push back the lambda into the vector, modify a and b appropriately, and voila.

Stylistically though, I actually strongly feel you should use the struct. It's much clearer what's going on. The mere fact that you are seeming to want to capture a and b in one place, and test against c in another, means that this is used in your code in not just one place. In exchange for like, 2 extra lines of code, you get something more readable, easier to debug, and more extensible.

Using functor in lambda

Thanks to Remy Lebeau's directions, I have managed to figure out what I needed to do: I had to add const to the declarations of my operator functions, e.g.,

int_fast32_t operator()() const override {
return file_context->read1();
}

in an Argument implementation, with the abstract base class defining the method as

virtual std::int_fast32_t operator()() const = 0;

Then I was able to write my assignments as, e.g.,

opcodes[64] = [=](){ _paint(_1byte()); };

and all was well (or at least doesn't generate compiler errors—we'll see once I've finished updating my code). I've even managed to improve another piece of code using std::generate to have a lambda make a lambda:

std::generate_n(opcodes.begin(), 64, [&index, _paint](){ auto rv = [=](){_paint(index);  }; ++index; return rv; });

and with any luck, all will be well (although since I have a similar construction happening later in the fill, I'll likely refactor the lambda as a function).



Related Topics



Leave a reply



Submit