【问题标题】:Capturing a copy of parameter pack捕获参数包的副本
【发布时间】:2018-10-28 16:08:43
【问题描述】:

我的班级有一个optional<A> 类型的成员。我正在尝试实现函数emplaceWhenReady,它获取A的构造函数的参数列表,但重要的部分是A只能在某个事件之后初始化。在事件之前调用emplaceWhenReady 时,我需要以某种方式捕获初始化值。

对于单个构造函数参数,代码可以写成:

struct B {
   bool _ready;
   std::optional<A> _a;
   std::function<void()> _fInit;

   template<typename ARG>
   void emplaceWhenReady1(ARG&& arg) {
      if (_ready) {
         _a.emplace(std::forward<ARG>(arg));
      } else {
         _fInit = [this, argCopy = std::forward<ARG>(arg)]() {
            _a.emplace(std::move(argCopy));
      };
    }
};

现在可以在类变为_ready 时调用_fInit()。但是我没有为多个参数编写类似的代码:

// Fails to compile
template<typename... ARGS>
void emplaceWhenReady(ARGS&&... args) {
    if (_ready) {
        _a.emplace(std::forward<ARGS>(args)...);
    } else {
        _fInit = [this, argsCopy = std::forward<ARGS>(args)...]() {
            _a.emplace(std::move(argsCopy)...);
        };
    }
}

天箭:https://godbolt.org/z/Fi3o1S

error: expected ',' or ']' in lambda capture list
       _fInit = [this, argsCopy = std::forward<ARGS>(args)...]() {
                                                          ^

感谢任何帮助!

【问题讨论】:

  • 我不太明白如何测试,你想在 optional 中放置一个可变参数列表?
  • 你知道std::function 强加了可复制性要求吗?不管怎样,看看&lt;tuple&gt;,特别是std::apply()
  • @ShafikYaghmour 谢谢,但从我看来,这个问题的问题是不同的,因为他们只需要将参数转发到 lambda 中,而不需要捕获参数的副本
  • :-( 这就是我得到的速度评论
  • @Deduplicator 我创建了一个使用make_tuple 存储参数副本的版本,它使用std::apply,正如您所建议的:godbolt.org/z/qC7I8L 对我来说似乎是一个可行的解决方案,谢谢!跨度>

标签: c++ lambda capture


【解决方案1】:

如果我们查看提案p0780: Allow pack expansion in lambda init-capture,它涵盖了这个问题和可能的解决方案:

随着广义 lambda 捕获 [1] 的引入,lambda 捕获几乎可以是任意复杂的并且几乎可以解决所有问题 问题。但是,功能上仍然存在一个尴尬的漏洞 关于参数包的 lambda 捕获:你只能 通过复制、引用或... std::tuple 捕获包?

考虑一个简单的例子,试图包装一个函数及其 参数转换为可调用的,以便稍后访问。如果我们复制一切, 该实现既易于编写又易于阅读:

template<class F, class... Args>
auto delay_invoke(F f, Args... args) {
    // the capture here can also be just [=]
    return [f, args...]() -> decltype(auto) {
        return std::invoke(f, args...);
    };
}

但是,如果我们尝试提高实施效率并尝试 将所有参数移到 lambda 中?看来你应该是 能够使用初始化捕获和写入:

template<class F, class... Args>
auto delay_invoke(F f, Args... args) {
    return [f=std::move(f), ...args=std::move(args)]() -> decltype(auto) {
        return std::invoke(f, args...);
    };
}

但这违反了非常明确的措辞 [expr.prim.lambda.capture]/17,强调我的:

后跟省略号的简单捕获是包扩展。后跟省略号的初始化捕获格式不正确。

它讨论了各种解决方案,包括使用元组:

由于这个限制,我们唯一的选择是将所有 args... 到 std::tuple 中。但是一旦我们这样做了,我们就无法访问 到参数作为参数包,所以我们需要把它们拉回来 体中的元组,使用类似 std::apply():

template<class F, class... Args>
auto delay_invoke(F f, Args... args) {
    return [f=std::move(f), tup=std::make_tuple(std::move(args)...)]() -> decltype(auto) {
        return std::apply(f, tup);
    };
}

如果我们想对捕获的内容进行处理,情况会变得更糟 参数包是调用命名函数而不是捕获的 目的。到那时,所有的理解都消失了 窗口:

这个提议是merged in the draft standard in March,所以我们应该在 C++2a 中得到这个改变。

【讨论】:

  • 我可以使用tuple:godbolt.org/z/8Nr2Ee。情况有点不同:我的函数从转发引用而不是值开始,但核心部分是相同的。
【解决方案2】:

借助一些帮助(请参阅上面的答案和 cmets),我能够找到的解决方案是:

    template<typename... ARGS>
    void emplaceWhenReady(ARGS&&... args) {
      if (_ready) {
        _a.emplace(std::forward<ARGS>(args)...);
      } else {
        _fInit = [this, argsCopy = std::make_tuple(std::forward<ARGS>(args)...)]() {
            auto doEmplace = [&](auto&... params) {
                _a.emplace(std::move(params)...);
            };
            std::apply(doEmplace, argsCopy);
        };
      }
    }

【讨论】:

    猜你喜欢
    • 2017-04-13
    • 1970-01-01
    • 1970-01-01
    • 2018-09-29
    • 1970-01-01
    • 2014-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多