Шаблон лямбда против функтора с оператором шаблона () - PullRequest
0 голосов
/ 03 июля 2019

Вдохновленный этим вопросом , я бы хотел сравнить использование c++20 шаблона лямбда с функтором, имеющим шаблон operator().

. В качестве контрольного примера рассмотримшаблонная функция call, которая принимает лямбда-шаблон в качестве аргумента и вызывает эту лямбду, создавая его экземпляр с некоторыми параметрами шаблона.Следующий код c++20 иллюстрирует идею.

#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;
}

В c++11 / c++14 / c++17 без лямбды-шаблона та же задача может быть реализована с помощью функтора, имеющего шаблонoperator(), как в следующем коде.

#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);

}

Основной недостаток, который я вижу во втором примере, заключается в том, что для эмуляции лямбда-захвата необходимо явно передавать все локальные переменные (в данном случае int a) функтору.Это может быть утомительно, если Functor::operator() нужно много переменных от своего "владельца".В конце концов, можно также передать указатель this на Functor::operator().Таких осложнений нет в примере c++20, где лямбда-захват заботится о захвате необходимых переменных.

Помимо простоты, есть ли какое-либо другое конкретное различие между двумя подходами, описанными выше?А как насчет эффективности?

1 Ответ

2 голосов
/ 03 июля 2019

Главный недостаток, который я вижу во втором примере, заключается в том, что для эмуляции лямбда-захвата необходимо явно передать все локальные переменные (в данном случае int a) функтору.

Я не согласен.

Вы можете видеть лямбду почти как класс / структуру с operator() и некоторым членом, соответствующим захваченным переменным.

Таким образом, вместо

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

вы можете написать (уже из 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; }
 };

и использовать его следующим образом

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

Основной недостаток, который я вижу с функторами (и я полагаю, этосомнительно, что недостатком является то, что вы не можете просто захватить, по ссылке или по значению ([&] или [=]), все внешние переменные, но вы должны явно перечислить все переменные, которые вы используете, как при определении функтораи при инициализации объекта функтора.

Не по теме: обратите внимание, что я пометил const operator() в моей nal структуре.

Если вы не измените захваченные переменные, лямбда-выражения эквивалентныфункторы с константой operator().

Это важно, если вы передадите функтор как постоянную ссылку

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
} 

, и вы получите ошибку компиляции, если operator() не const

...