【问题标题】:template lambda vs functor with template operator()模板 lambda 与带有模板运算符()的仿函数
【发布时间】:2019-11-13 21:17:40
【问题描述】:

this question 的启发,我想将c++20 模板lambda 的使用与具有模板operator() 的函子进行比较。

作为一个测试用例,考虑一个模板函数call,它接受一个模板 lambda 作为参数,并调用这个 lambda 并使用一些模板参数对其进行实例化。下面的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 中,没有模板 lambda,可以使用具有模板 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);

}

我在第二个示例中看到的主要缺点是,为了模拟 lambda 捕获,需要将所有局部变量(在本例中为 int a)显式传递给函子。如果Functor::operator() 需要来自其“所有者”的许多变量,这可能会很乏味。最终,也可以将指针this 传递给Functor::operator()c++20 示例中不存在此类复杂情况,其中 lambda 捕获负责捕获所需的变量。

除了简单之外,上述两种方法之间还有其他具体区别吗?效率怎么样?

【问题讨论】:

  • 这与普通函子和泛型 lambda 或(完全可推导的)模板化函子和泛型 lambda 之间的比较基本相同。
  • @Quentin 比较可能类似,但是例如,您不能使用成员模板定义本地类,因此您需要像第二个代码中那样在 main 之外声明函子,而 c第一个代码的 ++20 lambda 是本地的。
  • 似乎您回答了自己的问题。顺便说一句,这没有错,但最好将答案放在答案中,而不是问题和 cmets...
  • C++14 已经有了通用 lambda,可以使用显式模板参数调用(使用丑陋的 operator()&lt;&gt; 语法)。 C++20 仅允许为它们提供显式模板 参数 列表(这对概念特别有用)。
  • @DavisHerring 但如何使用非类型模板参数?

标签: c++ templates lambda variadic-templates c++20


【解决方案1】:

我在第二个示例中看到的主要缺点是,为了模拟 lambda 捕获,需要将所有局部变量(在本例中为 int a)显式传递给函子。

我不同意。

您几乎可以将 lambda 视为具有 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);

我看到函子的主要缺点(我认为这是一个缺点值得怀疑)是你不能简单地通过引用或值([&amp;][=])捕获所有外部变量,但你在定义仿函数和初始化仿函数对象时,必须明确列出您使用的所有变量。

题外话:注意我在nal 结构中标记了const operator()

如果不修改捕获的变量,则 lambda 等效于具有常量 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,则会出现编译错误

【讨论】:

  • 在写 我看到的仿函数的主要缺点... 我的意思是你需要将所有相关的变量传递给仿函数,不管你怎么做(构造函数在您的答案中,或在我的代码中为operator() 提供参数)。如果您更改函子的代码,并且需要更多变量,您还必须更改声明和/或构造函数。使用 capture [&] 你会自动获得所有这些。
  • @francesco - 好的:我们同意这一点;我想说的是,您不需要将捕获的变量作为operator() 的参数传递。
  • 通过在 struct nal 中创建引用,我认为与 lambda 相比,您有额外的开销。
  • @francesco - 我认为常量引用不会有很大的开销;无论如何(1)据我所知,这正是通过引用捕获 lambda 所发生的事情(除了 lambda 都是隐式完成的)和(2)也在你的例子中(你将a 作为operator() 的参数传递) 你使用一个常量引用。
  • 是的,同样将a 传递给operator() 你使用了一个引用,但是编译器应该能够优化它,如果你这样做可能会更困难在nal 中创建一个在operator() 退出后仍然存在的引用。
猜你喜欢
  • 2010-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多