【问题标题】:Capture by universal reference通过通用参考捕获
【发布时间】:2014-02-09 21:16:09
【问题描述】:

当将推导类型作为 r 值引用传递时,我获得了通用引用功能,并且可以像这样实现完美转发:

template <typename T>
void func(T&& t) {
    other_func(std::forward<T>(t));
}

...由于 T 的派生方式和标准的参考折叠规则。

现在考虑 other_func 接受一个函数对象

template <typename T>
void func(T&& t) {
    other_func([](int v) { return t + v; }); // I chose addition for example purposes
}

现在显然这不会编译,因为 t 没有被捕获。我的问题是:我如何捕获它,以便捕获的值将是 T 推导出的任何值?

这是否可以使用新的通用 lambda 捕获?如果……怎么办?

[t = std::forward<T>(t)] ? 

我仍然没有真正了解新捕获初始化程序的机制...

【问题讨论】:

  • 快速说明:当前接受的名称是转发引用,而不是通用引用

标签: c++ lambda c++14


【解决方案1】:

您可以在 C++11 中“通过通用引用捕获”,因为模板参数 T 的类型可用于 lambda 函数 (hideous live code example at Coliru):

template <typename T>
void func(T&& t) {
  other_func([&t](int v) {
    return std::forward<T>(t) + v;
  });
}

【讨论】:

  • 这很有趣 - 认为 lambda 内的 t 类型会折叠为左值引用。另一方面:您在运算符声明之后编写的引用说明符的名称是什么?不知道这些存在
  • @RichardVock 这些是ref-qualifiers。很像成员函数上的const-qualifiers,它们限制了可以调用函数的对象。例如,当您在右值对象上调用 foo 时,会调用 void foo() &amp;&amp;
  • 一个可能的问题:如果 other_func 存储该 lambda 并且它的持续时间比 func 的主体范围更长,则调用 lambda 是 UB,因为捕获的变量 t 已离开范围。跨度>
  • 我找到了这个 SO 问题,因为我正在寻找类似 @​​987654331@ 的东西(或[&amp;=] )。我的意思是——引用类型将通过引用保存在 lambda 的this 中,值类型或右值引用类型的变量将通过值保存。这样的事情可能吗?这样,可能的范围问题(在上面的评论中)就不会成为问题了。特别是在我的情况下说没有other_func,而是我在func中返回lambda。如果 T 是引用类型,则不会进行复制,但如果 T&& 是右值,它会安全地存储在 lambda 的 this 中。
  • 我指定的用例实际上是编写返回范围的函数(range/v3 库)时的常见用例。该函数接受参数,引用或值类型,并且转换/适应用户定义的 lambda 几乎总是希望使用所有参数。所以对于每一个transform lambda,目前的解决方案是通过引用或值手动捕获所有参数,基本上是重复外壳函数的参数。
【解决方案2】:

好的,让我们试试这个。不幸的是,我手头没有支持此功能的编译器,所以如果我在此过程中严重误解了事情,请原谅我。

涉及此的提案是N3648

这里有趣的部分是 init 捕获中的变量类型是通过auto 推导出来的:

该成员的类型对应于假设的类型 “auto init-capture ;”形式的变量声明[...]。

所以你从捕获列表[c = std::forward&lt;T&gt;(t)] 得到什么的问题等同于你从声明auto c = std::forward&lt;T&gt;(t) 得到什么。

这里推导出的类型将是std::remove_reference&lt;T&gt;::type(引用限定符被auto 删除),所以你总是会在这里得到一个新值。如果t 是一个右值引用,您将移动构造该新值,否则您将复制构造(由于std::forward 的返回值)。

好消息是这个新值归 lambda 所有。所以不管你最初传入的是什么t,从捕获的cstd::move 都是安全的。因此,即使您不再知道初始 t 的类型,您仍然没有丢失任何东西。

【讨论】:

  • 我认为您需要一个包装器类型来支持完美转发。右值引用成员将不允许复制,并且移动构造函数不需要可用于 lambda。
  • @dyp 是的,包装器类型可以工作。但是,它也会很丑陋(std::auto_ptr_ref,有人吗?)。有趣的是,我不认为我们在这里牺牲完美转发而遭受任何重大损失:如果我们传递一个右值和一个副本,我们仍然可以逃脱。
  • 没错,但移动也可能很昂贵,例如当成员是仅复制的(旧数据类型、数组、..)。我想到了template&lt;class T&gt; wrapper { T m; }; template&lt;class T&gt; auto wrap(T&amp;&amp; p) -&gt; wrapper&lt;T&amp;&amp;&gt; { return {std::forward&lt;T&gt;(p)}; },然后是[t = wrap(std::forward&lt;T&gt;(t))])
  • 感谢您的研究工作 UglyFontMS ;) 连同 dyp 建议的包装器,这对我来说绝对是一个解决方案。您可以考虑将其纳入您的答案 - 无论哪种方式我都会接受。
  • 仅供参考,coliru 的 clang++ 是 3.4 发布后不久的主干版本,因此它支持大多数/所有 C++14 语言功能。
【解决方案3】:

为了通过引用捕获实现所需的行为,不需要 C++14 的通用 lambda 捕获(但与通过引用捕获一样,需要注意不要创建悬空引用):

template <typename T>
void func(T&& t) {
    other_func([&t](int v) { return  std::forward<T>(t) + v; });
}

相反,如果决定使用按值捕获,则应将 lambda 标记为可变以允许有效移动(因为 const 限定符隐式添加到 lambda):

template <typename T>
void func(T&& t) {
    other_func([t = std::forward<T>(t)](int v) mutable { return  std::move(t) + v; });
}

【讨论】:

  • 上例中的 lambda 不按值捕获 - 因此 mutable 没有必要在那里(确实是避免可变和 C++14 泛型 lambda 捕获的好方法)
  • @user1115339:澄清一下,当你说“因为 const 限定符被隐式添加到 lambdas”时,你的意思是 value 捕获的变量是隐式 const (在这种情况下是 t,而不是 lambda 表达式本身)?
猜你喜欢
  • 2015-12-29
  • 1970-01-01
  • 1970-01-01
  • 2015-07-26
  • 1970-01-01
  • 2015-01-28
  • 2021-04-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多