15

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;
}
Jendas
  • 3,359
  • 3
  • 27
  • 55

2 Answers2

7

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.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Wow, ok, that looks complicated. But would your solution deal with the code I posted in edit? – Jendas Aug 25 '15 at 16:34
  • 1
    @Jendas sorry, `task_once` had a problem with `void` return type. Fixed: see the new live example. Just replace `std::function` with `task_once`, and use move-capturing mutable lambdas instead of `std::bind`. `std::bind` is rarely a good idea. – Yakk - Adam Nevraumont Aug 25 '15 at 17:15
  • @Yakk: I came up with a similar solution as your C++11 one in your edit. Am I correct in saying that you can only call the std::function once, at which point it has consumed your object and you should not call it a second time? – AndyG Aug 25 '15 at 17:24
  • @AndyG if you call it a second time (or a copy of it a second time), you'll end up calling it on a moved-from object. This may or may not be what you want to happen. In every case, the OP's function object can only be called once (prior to a call on a moved-from object), as we have a move-only captured value calling a function that takes its argument by value. – Yakk - Adam Nevraumont Aug 25 '15 at 17:26
2

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

Community
  • 1
  • 1
AndyG
  • 39,700
  • 8
  • 109
  • 143
  • I appreciate your quick answer, but I doubt that approach is equivalent to the usage of bind. Correct me if I am wrong but if something would call destructor before `a()`, the code would result in undefined behaviour or something like that. That certainly isn't anything I need. – Jendas Aug 25 '15 at 16:28
  • I reduced your code to - http://coliru.stacked-crooked.com/a/62d45f89ae2911bc which looks very much as something I need. But how does it work? – Jendas Aug 25 '15 at 16:55
  • @Jendas: Sorry, please don't do that. The move construction doesn't happen until the call to the function, at which point your variable has already been destructed, so it can cause undefined behavior. – AndyG Aug 25 '15 at 17:05
  • Yes, I thought so :-/ It looks ok, but is very much in the undefined behaviour area. That sux. – Jendas Aug 25 '15 at 17:06
  • @Jendas: I think this will do what you want, but you can only ever call your function once! http://coliru.stacked-crooked.com/a/c98b9e2b1071a916 – AndyG Aug 25 '15 at 17:20
  • @Jendas: I should note that the code in that link is C++14. For a C++11 equivalent see Yakk's edit. – AndyG Aug 25 '15 at 17:26