捕获参数包的副本

时间:2018-10-28 16:08:44

标签: c++ lambda capture

我的班级的成员类型为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));
      };
    }
};
现在,当类变为_fInit()时,可以调用

_ready。但是我无法为多个参数编写类似的代码:

// 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)...);
        };
    }
}

Godbolt:https://godbolt.org/z/Fi3o1S

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

感谢您的帮助!

2 个答案:

答案 0 :(得分: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中进行此更改。

答案 1 :(得分:1)

在一些帮助下(请参见上面的答案和评论),我能够找到的解决方案是:

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