Reputation: 7539
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
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