C++ 将仅移动结构绑定到函数

C++ 将仅移动结构绑定到函数,c++,c++11,stdbind,C++,C++11,Stdbind,我需要将带有已删除副本构造函数的结构绑定到函数。我已将我试图实现的目标简化为以下最小示例: struct Bar { int i; Bar() = default; Bar(Bar&&) = default; Bar(const Bar&) = delete; Bar& operator=(const Bar&) = delete; }; void foo(Bar b) { std::cout <&l

我需要将带有已删除副本构造函数的结构绑定到函数。我已将我试图实现的目标简化为以下最小示例:

struct Bar {
    int i;
    Bar() = default;
    Bar(Bar&&) = default;
    Bar(const Bar&) = delete;
    Bar& operator=(const Bar&) = delete;
};

void foo(Bar b) {
    std::cout << b.i << std::endl;
}

int main()
{
    Bar b;
    b.i = 10;

    std::function<void()> a = std::bind(foo, std::move(b)); // ERROR
    a();

    return 0;
}

std::function
无法执行仅可调用的移动。它会删除传入的类型,以便调用(带有签名)、销毁和复制。1

编写一个move only
std::function
只是一点工作。在不同的背景下

std::packaged_task
也是一个仅移动类型的橡皮擦调用程序,但它的重量可能比您想要的要重,而且获取值是一件痛苦的事情

更简单的解决方案是滥用共享指针:

template<class F>
auto shared_function( F&& f ) {
  auto pf = std::make_shared<std::decay_t<F>>(std::forward<F>(f));
  return [pf](auto&&... args){
    return (*pf)(decltype(args)(args)...);
  };
}

请注意,您只能在带有上述
任务的右值上下文中调用一次
()
。这是因为
()
具有破坏性,在您的情况下应该是这样

遗憾的是,上面的内容依赖于C++14。现在我不喜欢写C++11代码。因此,这里有一个性能较差的更简单的C++11解决方案:

std::function<void()> a;
{
    Bar b;
    b.i = 10;
    auto pb = std::make_shared<Bar>(std::move(b));
    a = [pb]{ return foo(std::move(*pb)); };
}
a();
std::函数a;
{
b栏;
b、 i=10;
自动pb=std::使_共享(std::移动(b));
a=[pb]{returnfoo(std::move(*pb));};
}
a();
这会将
b
的移动副本推入共享指针,存储在
std::function
中,然后在您第一次调用
()
时以破坏性方式使用它


1它在没有它的情况下实现move(除非它使用小函数优化,我希望它使用类型的move)。它还实现了转换回原始类型,但每个类型都支持这种转换。对于某些类型,它支持检查null(即显式转换为bool),但我确实不确定它这样做的确切类型。

您可以通过指针、lambda和std::bind的组合绕过
std::function
的约束:

auto lambda = [](Bar* b){::foo(std::move(*b));};
std::function<void()> a = std::bind(lambda, &b);
a();

我感谢您的快速回答,但我怀疑这种方法是否等同于使用bind。如果我错了,请纠正我,但是如果有东西在
a()
之前调用析构函数,那么代码将导致未定义的行为或类似的行为。这当然不是我需要的。我将您的代码缩减为-这看起来非常像我需要的东西。但它是如何工作的?@Jendas:对不起,请不要这样做。在调用函数之前,移动构造不会发生,此时变量已经被破坏,因此它可能会导致未定义的行为。是的,我认为是这样:-/它看起来不错,但非常位于未定义的行为区域。詹达斯:我想这会满足你的要求,但你只能调用你的函数一次!哇,好吧,看起来很复杂。但是您的解决方案会处理我在编辑中发布的代码吗?@Jendas抱歉,
task\u有一次
void
返回类型出现问题。修复:请参阅新的实时示例。只需将
std::function
替换为
task\u一次
,并使用移动捕获可变lambda而不是
std::bind
std::bind
很少是个好主意。@Yakk:我在编辑中提出了一个与C++11类似的解决方案。我说的std::函数只能调用一次,在这一点上它已经消耗了你的对象,你不应该再调用它了,对吗?@AndyG如果你再调用它一次(或者再调用它的一个副本),你会在一个moved from对象上调用它。这可能是你想要的,也可能不是。在每种情况下,OP的function对象只能调用一次(在调用moved from对象之前),因为我们有一个move only捕获的值调用一个按值获取参数的函数。
template<class Sig>
struct task_once;

namespace details_task_once {
  template<class Sig>
  struct ipimpl;
  template<class R, class...Args>
  struct ipimpl<R(Args...)> {
    virtual ~ipimpl() {}
    virtual R invoke(Args&&...args) && = 0;
  };
  template<class Sig, class F>
  struct pimpl;
  template<class R, class...Args, class F>
  struct pimpl<R(Args...), F>:ipimpl<R(Args...)> {
    F f;
    template<class Fin>
    pimpl(Fin&&fin):f(std::forward<Fin>(fin)){}
    R invoke(Args&&...args) && final override {
      return std::forward<F>(f)(std::forward<Args>(args)...);
    };
  };
  // void case, we don't care about what f returns:
  template<class...Args, class F>
  struct pimpl<void(Args...), F>:ipimpl<void(Args...)> {
    F f;
    template<class Fin>
    pimpl(Fin&&fin):f(std::forward<Fin>(fin)){}
    void invoke(Args&&...args) && final override {
      std::forward<F>(f)(std::forward<Args>(args)...);
    };
  };
}
template<class R, class...Args>
struct task_once<R(Args...)> {
  task_once(task_once&&)=default;
  task_once&operator=(task_once&&)=default;
  task_once()=default;
  explicit operator bool() const { return static_cast<bool>(pimpl); }

  R operator()(Args...args) && {
    auto tmp = std::move(pimpl);
    return std::move(*tmp).invoke(std::forward<Args>(args)...);
  }
  // if we can be called with the signature, use this:
  template<class F,
    class R2=R,
    std::enable_if_t<
        std::is_convertible<std::result_of_t<F&&(Args...)>,R2>{}
        && !std::is_same<R2, void>{}
    >* = nullptr
  >
  task_once(F&& f):task_once(std::forward<F>(f), std::is_convertible<F&,bool>{}) {}

  // the case where we are a void return type, we don't
  // care what the return type of F is, just that we can call it:
  template<class F,
    class R2=R,
    class=std::result_of_t<F&&(Args...)>,
    std::enable_if_t<std::is_same<R2, void>{}>* = nullptr
  >
  task_once(F&& f):task_once(std::forward<F>(f), std::is_convertible<F&,bool>{}) {}

  // this helps with overload resolution in some cases:
  task_once( R(*pf)(Args...) ):task_once(pf, std::true_type{}) {}
  // = nullptr support:
  task_once( std::nullptr_t ):task_once() {}

private:
  std::unique_ptr< details_task_once::ipimpl<R(Args...)> > pimpl;

// build a pimpl from F.  All ctors get here, or to task() eventually:
  template<class F>
  task_once( F&& f, std::false_type /* needs a test?  No! */ ):
    pimpl( new details_task_once::pimpl<R(Args...), std::decay_t<F>>{ std::forward<F>(f) } )
  {}
  // cast incoming to bool, if it works, construct, otherwise
  // we should be empty:
  // move-constructs, because we need to run-time dispatch between two ctors.
  // if we pass the test, dispatch to task(?, false_type) (no test needed)
  // if we fail the test, dispatch to task() (empty task).
  template<class F>
  task_once( F&& f, std::true_type /* needs a test?  Yes! */ ):
    task_once( f?task_once( std::forward<F>(f), std::false_type{} ):task_once() )
  {}
};
std::function<void()> a;
{
    Bar b;
    b.i = 10;
    auto pb = std::make_shared<Bar>(std::move(b));
    a = [pb]{ return foo(std::move(*pb)); };
}
a();
auto lambda = [](Bar* b){::foo(std::move(*b));};
std::function<void()> a = std::bind(lambda, &b);
a();
std::function<void()> a = [&b](){::foo(std::move(b));};
a()
std::function<void()> a;
{
    Bar b;
    b.i = 10;
    a = [b2 = std::make_shared<decltype(b)>(std::move(b))]()
    {
        // move the underlying object out from under b2
        // which means b2 is in a valid but undefined state afterwards
        ::foo(std::move(*b2));
    }; 
}