francesco
francesco

Reputation: 7539

template lambda vs functor with template operator()

Inspired by this question, I would like to compare the use of c++20 template lambda with a functor having a template operator().

As a test case, consider a template function call which takes a template lambda as an argument, and call this lambda instantiating it with some template parameters. The following c++20 code exemplifies the idea.

#include <tuple>
#include <utility>

template <int I, class Lambda, class... ArgsType>
void call(Lambda&& F, ArgsType&& ...args)
{
           F.template operator()<I>(std::forward<ArgsType>(args)...);
}

int main() {
   std::tuple<int, double, int> t{0,0,0};
   int a = 2;

   auto f = [&]<int I>(auto& x) { std::get<I>(x) += I + a; };
   call<0>(f, t);

   return 0;
}

In c++11/c++14/c++17, not having template lambda, the same task can be implemented with a functor, having a template operator(), as in the following code.

#include <tuple>
#include <utility>

template <int I, class Lambda, class... ArgsType>
void call(Lambda&& F, ArgsType&& ...args)
{
           F.template operator()<I>(std::forward<ArgsType>(args)...);
}

struct Functor {
    template <int I, class T>
    void operator()(const int& a, T& x) { std::get<I>(x) += I + a; };
};

int main() {
   std::tuple<int, double, int> t{0,0,0};
   int a = 2;

   Functor func{};
   call<0>(func, a, t);

}

The main disadvantage I see in the second example is that, to emulate the lambda capture, one needs to pass explicitly all local variables (in this case int a) to the functor. This can be tedious, if Functor::operator() needs many variables from its "owner". Eventually, one could also perhaps pass the pointer this to Functor::operator(). No such complications are present in the c++20 example, where the lambda capture takes care of capturing the needed variables.

Aside from simplicity, is there any other concrete difference between the two approaches outlined above? What about the efficiency?

Upvotes: 0

Views: 692

Answers (1)

max66
max66

Reputation: 66210

The main disadvantage I see in the second example is that, to emulate the lambda capture, one needs to pass explicitly all local variables (in this case int a) to the functor.

I disagree.

You can see a lambda almost as a class/struct with an operator() and some member corresponding to the captured variables.

So instead of

[&]<int I>(auto& x) { std::get<I>(x) += I + a; };

you can write (already from C++11)

struct nal // not a lambda
 {
   int const & a;

   template <int I, typename T>
   auto operator() (T & x) const
    { std::get<I>(x) += I + a; }
 };

and use it as follows

call<0>(nal{a}, t);

The main disadvantage I see with functors (and I suppose it's questionable that is a disadvantage) is that you can't simply capture, by reference or by value ([&] or [=]), all external variables but you have to explicitly list all the variable you use, both in defining the functor and in initializing the functor object.

Off Topic: observe that I've tagged const the operator() in my nal structure.

If you don't modify the captured variables, the lambdas are equivalent to functors with constant operator().

This is important if you pass the functor as constant reference

template <int I, class Lambda, class... ArgsType>
void call (Lambda const & F, ArgsType&& ...args)
{ // ......^^^^^^^^^^^^^^  now F is a constant reference
  F.template operator()<I>(std::forward<ArgsType>(args)...); // compilation error
                                                             // if operator()
                                                             // isn't const
} 

you get a compilation error if operator() isn't const

Upvotes: 2

Related Questions