【问题标题】:When defining a function, what type is a lambda function/expression?定义函数时,lambda 函数/表达式是什么类型?
【发布时间】:2015-05-06 20:21:34
【问题描述】:

我想定义一个函数,它接受(除了通常的输入参数)一个 lambda 函数。我想尽可能地限制这个函数(它自己的输入和返回类型)。

int myfunc( const int a, LAMBDA_TYPE (int, int) -> int mylamda )
{
    return mylambda( a, a ) * 2;
}

这样我就可以这样调用函数了:

int input = 5;
myfunc( input, [](int a, int b) { return a*b; } );

定义myfunc的正确方法是什么?

有没有办法定义一个默认的 lambda?像这样:

int myfunc( const int a, LAMBDA_TYPE = [](int a, int b) { return a*b; });

【问题讨论】:

  • 你可以使用模板或 std::function,因为所有的 lambdas 都需要转换成 std​​::function
  • 所以定义是 – int myfunc( const int a, std::function mylambda = [](int a, int b) { return a*b; });
  • @S.H 使用函数模板可能会更好。 std::function 可能会产生一些不必要的开销。
  • 对于默认 lambda,请参阅此问题:stackoverflow.com/q/6025118/4834

标签: c++ c++11 lambda


【解决方案1】:

如果你使用std::function<int(int,int)>,它会产生开销,但它会做你想做的事。它甚至可以在 C++14 中正确重载。

如果您不希望 std::function 的类型擦除和分配开销,您可以这样做:

template<
  class F,
  class R=std::result_of_t<F&(int,int)>,
  class=std::enable_if_t<std::is_same<int,R>{}>
>
int myfunc( const int a, F&& f )

或检查可转换为int 而不是相同。这是 sfinae 解决方案。 1

这将正常过载。为了简洁起见,我使用了一些 C++14 特性。在 C++11 中将 blah_t&lt;?&gt; 替换为 typename blah&lt;?&gt;::type

另一个选项是static_assert 一个类似的子句。这会生成最好的错误消息。

终于可以直接使用了:如果不能按照你使用的方式使用,代码就会编译失败。

在 C++1z 概念中,将有更简单/更少的代码沙拉方法来完成 sfinae 解决方案。


1 在某些编译器上,std::result_of 无法与 sfinae 配合使用。在这些上,将其替换为decltype(std::declval&lt;F&amp;&gt;()(1,1))。除非您的编译器不支持 C++11(如 msvc 2013 和 2015),否则这将起作用。 (幸运的是 2013/3015 有一个不错的result_of)。

【讨论】:

  • 请注意,这并不要求 lambda 参数是int,只是int 可以隐式转换为 lambda 所采用的任何参数。它确实要求使用两个 ints 调用 lambda 的结果产生一个 int
  • @DavidRodríguez-dribeas nod,在一般情况下检测到这一点几乎是不可能的。我也许可以使用多重继承技术和替代 SFINAE(除int 之外的任何东西)模板operator() 来实现它,该模板返回一个带有result_of 调用的私有标志类型,用于寻找替代方案,以及函数指针的特殊情况编码.如果传入的可调用对象是final,则无法工作...想不出另一种会破坏的情况。比较傻。另一种方法是使用{int},{int} 而不是int,int 来测试调用以捕获缩小的转换。
  • 您能否更详细地解释“类型擦除和分配开销”?
  • @S.H. std::function&lt;Signature&gt; 有一个构造函数来获取函数对象。正如 C++11 中所指定的,该构造函数接受 anything,但仅在传递的对象与 Signature 调用兼容时才编译(可在适当的情况下从 args 转换为 args 和返回值)。最近,添加了一个要求,即它仅在由于签名兼容性(早期检查)而能够编译时才参与重载解决方案。这允许仅基于std::function 签名差异重载的两个函数“正确”重载。
  • @S.H. std::function 不是免费的。它通常通过将存储的任何内容复制到堆中并通过虚拟方法访问它(调用、副本等)来实现。这两种操作都有与之相关的成本。虚拟调用(或任何替代实现)和堆分配都是其他解决方案不需要的额外成本。它用来接受几乎任何可调用的技术被称为“类型擦除”或“运行时概念”,而这个评论对于解释这一点来说太小了。实际上,虚拟调用的最大成本是阻止一些优化。
【解决方案2】:

有两种选择,类型擦除 std::function&lt;signature&gt; 强制使用与您的问题相符的特定签名,但它会产生一些额外的成本(主要是它通常不能内联)。另一种方法是使用模板并将最后一个参数保留为通用对象。从性能的角度来看,这种方法可能会更好,但是您将语法检查留给编译器(我认为这不是问题)。其中一个 cmets 提到将 lambda 作为函数指针传递,但这仅适用于没有捕获的 lambda。

我个人会选择第二种选择:

template <typename Fn>
int myFunc(const int a, Fn&& f) {
   return f(a, a) * 2;
}

请注意,虽然这不会显式强制特定签名,但编译器将强制 f 可以用两个 int 调用,并产生可以乘以 2 并转换为 int 的东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-14
    • 2018-08-06
    • 1970-01-01
    • 1970-01-01
    • 2021-01-31
    • 1970-01-01
    • 2010-09-06
    相关资源
    最近更新 更多