Search code examples
c++c++11stdbind

Pass by value a move-only structure to function


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

Solution

  • 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.