将不可复制的闭包对象传递给 std::function 参数

2021-12-23 00:00:00 lambda c++ move-semantics c++14

在 C++14 中,lambda 表达式可以通过使用捕获初始值设定项从变量中移动来捕获变量.然而,这使得生成的闭包对象不可复制.如果我有一个接受 std::function 参数的现有函数(我不能改变),我不能传递闭包对象,因为 std::function 的构造函数要求给定的函子是 CopyConstructible.

#include #include <内存>void doit(std::function f) {F();}int main(){std::unique_ptrp(new int(5));doit([p = std::move(p)] () { std::cout << *p << std::endl; });}

这会导致以下错误:

/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1911:10:错误:调用 '' 的隐式删除复制构造函数new _Functor(*__source._M_access<_Functor*>());^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1946:8: 注意:在成员函数'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>的实例化>::_M_clone' 在此处请求_M_clone(__dest, __source, _Local_storage());^/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2457:33: 注意:在成员函数'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>的实例化>::_M_manager' 在此处请求_M_manager = &_My_handler::_M_manager;^test.cpp:10:7: 注意:在函数模板特化的实例化中 'std::function::function<, void>'在这里请求doit([p = std::move(p)] () { std::cout << *p << std::endl; });^test.cpp:10:8: 注意:'' 的复制构造函数被隐式删除,因为字段 '' 已删除复制构造函数doit([p = std::move(p)] () { std::cout << *p << std::endl; });^/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/unique_ptr.h:273:7:注意:'unique_ptr' 已在此处明确标记为已删除unique_ptr(const unique_ptr&) = 删除;^

是否有合理的解决方法?

使用 Ubuntu clang 版本 3.5-1~exp1 (trunk) 进行测试

解决方案

有这个办法:

模板<类型名称签名>struct make_copyable_function_helper;模板<类型名称 R,类型名称... Args >struct make_copyable_function_helper<R(Args...)>{模板<类型名输入>std::function<R(Args...)>运算符()(输入&& i)const {自动 ptr = std::make_shared::type >( std::forward(i) );返回 [ptr]( Args... args )->R {返回 (*ptr)(std::forward(args)...);};}};模板make_copyable_function(输入&& i){return make_copyable_function_helper()( std::forward(i) );}

在这里我们创建一个指向我们数据的共享指针,然后创建一个捕获该共享指针的可复制 lambda,然后我们将该可复制 lambda 包装到请求签名的 std::function 中.>

在上述情况下,您只需:

doit( make_copyable_function( [p = std::move(p)] () { std::cout << *p << std::endl; }) );

稍微高级的版本推迟了类型擦除并添加了一层完美转发以减少开销:

templatestruct copyable_function {typedef typename std::decay::type stored_input;模板自动操作符()(Args&&...args)->decltype( std::declval()(std::forward(args)...)){返回 (*ptr)(std::forward(args));}copyable_function( input&& i ):ptr( std::make_shared( std::forward(i) ) ) {}copyable_function( copyable_function const& ) = default;私人的:std::shared_ptr指针;};模板<类型名输入>copyable_function<输入>make_copyable_function(输入&& i){返回 {std::forward(i)};}

它不需要您传入签名,并且在某些情况下效率会更高一些,但使用的技术更加晦涩.

在 C++14 中可以更简洁:

模板( std::forward(f) );返回 [spf](auto&&... args)->decltype(auto) {返回 (*spf)( decltype(args)(args)...);};}

完全不需要辅助类型.

In C++14, a lambda expression can capture variables by moving from them using capture initializers. However, this makes the resulting closure object non-copyable. If I have an existing function that takes a std::function argument (that I cannot change), I cannot pass the closure object, because std::function's constructor requires the given functor to be CopyConstructible.

#include <iostream>
#include <memory>

void doit(std::function<void()> f) {
    f();
}

int main()
{
    std::unique_ptr<int> p(new int(5));
    doit([p = std::move(p)] () { std::cout << *p << std::endl; });
}

This gives the following errors:

/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1911:10: error: 
      call to implicitly-deleted copy constructor of '<lambda at test.cpp:10:7>'
            new _Functor(*__source._M_access<_Functor*>());
                ^        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:1946:8: note: in
      instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>
      >::_M_clone' requested here
              _M_clone(__dest, __source, _Local_storage());
              ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/functional:2457:33: note: in
      instantiation of member function 'std::_Function_base::_Base_manager<<lambda at test.cpp:10:7>
      >::_M_manager' requested here
            _M_manager = &_My_handler::_M_manager;
                                       ^
test.cpp:10:7: note: in instantiation of function template specialization 'std::function<void
      ()>::function<<lambda at test.cpp:10:7>, void>' requested here
        doit([p = std::move(p)] () { std::cout << *p << std::endl; });
             ^
test.cpp:10:8: note: copy constructor of '' is implicitly deleted because field '' has a deleted
      copy constructor
        doit([p = std::move(p)] () { std::cout << *p << std::endl; });
              ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/bits/unique_ptr.h:273:7: note: 
      'unique_ptr' has been explicitly marked deleted here
      unique_ptr(const unique_ptr&) = delete;
      ^

Is there a reasonable workaround?

Testing with Ubuntu clang version 3.5-1~exp1 (trunk)

解决方案

There is this approach:

template< typename signature >
struct make_copyable_function_helper;
template< typename R, typename... Args >
struct make_copyable_function_helper<R(Args...)> {
  template<typename input>
  std::function<R(Args...)> operator()( input&& i ) const {
    auto ptr = std::make_shared< typename std::decay<input>::type >( std::forward<input>(i) );
    return [ptr]( Args... args )->R {
      return (*ptr)(std::forward<Args>(args)...);
    };
  }
};

template< typename signature, typename input >
std::function<signature> make_copyable_function( input && i ) {
  return make_copyable_function_helper<signature>()( std::forward<input>(i) );
}

where we make a shared pointer to our data, then make a copyable lambda that captures that shared pointer, then we wrap that copyable lambda into a std::function of the requested signature.

In your case above, you'd just:

doit( make_copyable_function<void()>( [p = std::move(p)] () { std::cout << *p << std::endl; } ) );

A slightly more advanced version defers the type erasure and adds a layer of perfect forwarding to reduce overhead:

template<typename input>
struct copyable_function {
  typedef typename std::decay<input>::type stored_input;
  template<typename... Args>
  auto operator()( Args&&... args )->
    decltype( std::declval<input&>()(std::forward<Args>(args)...) )
  {
    return (*ptr)(std::forward<Args>(args));
  }
  copyable_function( input&& i ):ptr( std::make_shared<stored_input>( std::forward<input>(i) ) ) {}
  copyable_function( copyable_function const& ) = default;
private:
  std::shared_ptr<stored_input> ptr;
};
template<typename input>
copyable_function<input> make_copyable_function( input&& i ) {
  return {std::forward<input>(i)}; 
}

which does not require you to pass the signature in, and can be slightly more efficient in a few cases, but uses more obscure techniques.

In C++14 with this can be made even more brief:

template< class F >
auto make_copyable_function( F&& f ) {
  using dF=std::decay_t<F>;
  auto spf = std::make_shared<dF>( std::forward<F>(f) );
  return [spf](auto&&... args)->decltype(auto) {
    return (*spf)( decltype(args)(args)... );
  };
}

doing away with the need for the helper type entirely.

相关文章