Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bind move-only structure to function

Tags:

c++

c++11

stdbind

I need to bind structure with deleted copy-constructor to a function. I have reduced what I am trying to achieve into following minimal example:

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

From the compiler I get only wailing and gnashing of teeth:

test.cpp:22:27: error: no viable conversion from 'typename _Bind_helper<__is_socketlike<void (&)(Bar)>::value, void (&)(Bar), Bar>::type' (aka '_Bind<__func_type (typename decay<Bar>::type)>') to 'std::function<void ()>'
    std::function<void()> a = std::bind(foo, std::move(b));
                          ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/functional:2013:7: note: candidate constructor not viable: no known conversion from 'typename _Bind_helper<__is_socketlike<void (&)(Bar)>::value, void (&)(Bar),
      Bar>::type' (aka '_Bind<__func_type (typename decay<Bar>::type)>') to 'nullptr_t' for 1st argument
      function(nullptr_t) noexcept
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/functional:2024:7: note: candidate constructor not viable: no known conversion from 'typename _Bind_helper<__is_socketlike<void (&)(Bar)>::value, void (&)(Bar),
      Bar>::type' (aka '_Bind<__func_type (typename decay<Bar>::type)>') to 'const std::function<void ()> &' for 1st argument
      function(const function& __x);
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/functional:2033:7: note: candidate constructor not viable: no known conversion from 'typename _Bind_helper<__is_socketlike<void (&)(Bar)>::value, void (&)(Bar),
      Bar>::type' (aka '_Bind<__func_type (typename decay<Bar>::type)>') to 'std::function<void ()> &&' for 1st argument
      function(function&& __x) : _Function_base()
      ^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/functional:2058:2: note: candidate template ignored: substitution failure [with _Functor = std::_Bind<void (*(Bar))(Bar)>]: no matching function for call to object of
      type 'std::_Bind<void (*(Bar))(Bar)>'
        function(_Functor);
        ^
1 error generated.

So I would like to ask whether there is any workaround that would allow me to bind Bar to foo while keeping Bar move-only.

Edit: Also consider following code where life of variable b ends before a is called:

int main()
{
    std::function<void()> a;
    {
        Bar b;
        b.i = 10;
        a = std::bind(foo, std::move(b)); // ERROR
    }
    a();

    return 0;
}
like image 934
Jendas Avatar asked Aug 25 '15 14:08

Jendas


2 Answers

std::function cannot take move-only invokables. It erases the passed in type down to invoke (with the signature), destroy, and copy.1

Writing a move-only std::function is only a bit of work. Here is a stab at it in a different context. live example.

std::packaged_task is amusingly also a move-only type-eraser invoker, but it is heavier weight than you probably want, and getting the value out is a pain.

An easier solution is to abuse a shared pointer:

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

which wraps some callable object into a shared pointer, puts that in a lambda perfect forwarding lambda.

This illustrates a problem -- the call doesn't work! All of the above have a const invokation.

What you want is a task that you can only call once.

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

live example.

Note that you can only invoke () in an rvalue context with the above task_once. This is because () is destructive, as it should be in your case.

Sadly, the above relies on C++14. And I don't like writing C++11 code nowadays. So, here is a simpler C++11 solution that is less performant:

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

This shoves a moved copy of b into a shared pointer, stores it within the std::function, then destructively consumes it the first time you invoke ().


1 It implements move without it (unless it uses the small function optimization, where I hope it uses the move of the type). It also implements convert-back-to-original type, but every type supports that. For some types, it supports check-for-null (ie, cast to bool explicitly), but I am honestly unsure of the exact types it does so to.

like image 153
Yakk - Adam Nevraumont Avatar answered Nov 13 '22 03:11

Yakk - Adam Nevraumont


You can get around the CopyConstructible constraint of std::function with a combination of pointers, lambdas, and std::bind:

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

Example


Edit

A one liner in C++11 with a lambda and capture-by-reference

std::function<void()> a = [&b](){::foo(std::move(b));};
a()

Example2

Edit2

(Moving comment into my answer)

Following your code edit which adds the constraint that the function object should be able to outlive the scope of the variable being bound to a function, we can accomplish this still with a lambda, only now we should capture a shared_ptr that uses allocation and move construction to hold a Bar.

In the example below I use C++14's generalized capture to capture the shared_ptr. @Yakk's solution translates this into C++11.

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

Example3

like image 2
AndyG Avatar answered Nov 13 '22 04:11

AndyG