Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Move-only version of std::function

Because std::function is copyable, the standard requires that callables used to construct it also be copyable:

n337 (20.8.11.2.1)

template<class F> function(F f);

Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R. The copy constructor and destructor of A shall not throw exceptions.`

This implies that it is not possible to form an std::function from a non-copyable bind object or a lambda that captured a move-only type such as std::unique_ptr.

It seems possible to implement such a move-only wrapper for move-only callables. Is there a standard library move-only equivalent for std::function or, is there a common workaround for this problem?

like image 900
orm Avatar asked Aug 15 '14 16:08

orm


People also ask

What does std :: move () do?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

How do you move a function in C++?

std::move in C++ Moves the elements in the range [first,last] into the range beginning at result. The value of the elements in the [first,last] is transferred to the elements pointed by result. After the call, the elements in the range [first,last] are left in an unspecified but valid state.

Can std :: function be copied?

You can recover the desired behavior by always using thread-local copies of the std::function because they'll each have an isolated copy of the state variables.


2 Answers

No, there is no move-only version of std::function in the C++ std library. (As of C++14)

Fastest possible delegates is an implementation of a std::function like class that happens to be faster than most std::function implementations in many std libraries, and it should be easy to fork into a move and copy version.

Wrapping your move only function object into a shared_ptr<F> in a class with a forwarding operator() is another approach.

Here is a task sketch:

template<class Sig> struct task;  namespace details {   template<class Sig>   struct task_iimpl;   template<class R, class...Args>   struct task_iimpl<R(Args...)> {     virtual ~task_iimpl() {}     virtual R invoke(Args&&...args) const = 0;   };   template<class F, class Sig>   struct task_impl;   template<class F, class R, class...Args>   struct task_impl<F,R(Args...)>:     task_iimpl<R(Args...)>   {     F f;     template<class T>     task_impl(T&& t):f(std::forward<T>(t)) {}     virtual R invoke(Args&&...args) const override {       return f( std::forward<Args>(args...) );     }   };   template<class F, class...Args>   struct task_impl<F,void(Args...)>:     task_iimpl<void(Args...)>   {     F f;     template<class T>     task_impl(T&& t):f(std::forward<T>(t)) {}     virtual void invoke(Args&&...args) const override {       f( std::forward<Args>(args...) );     }   }; } template<class R, class...Args> struct task<R(Args...)> {   virtual ~task_iimpl() {}   R operator()(Args...args) const {     return pImpl->invoke(std::forward<Args>(args...));   }   explicit operator bool()const{ return static_cast<bool>(pImpl); }   task(task &&)=default;   task& operator=(task &&)=default;   task()=default;    // and now for a mess of constructors   // the rule is that a task can be constructed from anything   // callable<R(Args...)>, destroyable, and can be constructed   // from whatever is passed in.  The callable feature is tested for   // in addition, if constructed from something convertible to `bool`,   // then if that test fails we construct an empty task.  This makes us work   // well with empty std::functions and function pointers and other tasks   // that are call-compatible, but not exactly the same:   struct from_func_t {};   template<class F,     class dF=std::decay_t<F>,     class=std::enable_if_t<!std::is_same<dF, task>{}>,     class FR=decltype(std::declval<F const&>()(std::declval<Args>()...)),     std::enable_if_t<std::is_same<R, void>{} || std::is_convertible<FR, R>{} >*=0,     std::enable_if_t<std::is_convertible<dF, bool>{}>*=0   >   task(F&& f):     task(       static_cast<bool>(f)?       task( from_func_t{}, std::forward<F>(f) ):       task()     )   {}   template<class F,     class dF=std::decay_t<F>,     class=std::enable_if_t<!std::is_same<dF, task>{}>,     class FR=decltype(std::declval<F const&>()(std::declval<Args>()...)),     std::enable_if_t<std::is_same<R, void>{} || std::is_convertible<FR, R>{} >*=0,     std::enable_if_t<!std::is_convertible<dF, bool>{}>*=0   >   task(F&& f):     task( from_func_t{}, std::forward<F>(f) )   {}    task(std::nullptr_t):task() {}   // overload resolution helper when signatures match exactly:   task( R(*pf)(Args...) ):     task( pf?task( from_func_t{}, pf ):task() )   {} private:   template<class F,     class dF=std::decay_t<F>   >   task(from_func_t, F&& f):     pImpl( std::make_unique<details::task_impl<dF,R(Args...)>>(       std::forward<F>(f)     )   {}    std::unique_ptr<details::task_iimpl<R(Args...)> pImpl; }; 

but it has not been tested or compiled, I just wrote it.

A more industrial strength version would include a small buffer optimization (SBO) to store small callables (assuming they are movable; if not movable, store on heap to allow moving), and a get-pointer-if-you-guess-the-type-right (like std::function).

like image 129
Yakk - Adam Nevraumont Avatar answered Sep 23 '22 21:09

Yakk - Adam Nevraumont


As others have pointed out, there is no move-only version of std::function in the library. Following is a work-around that the reuses (abuses?) std::function and allows it to accept move-only types. It is largely inspired by dyp's implementation in the comments, so a lot of the credit goes to him:

#include <functional> #include <iostream> #include <type_traits> #include <utility>  template<typename T> class unique_function : public std::function<T> {     template<typename Fn, typename En = void>     struct wrapper;      // specialization for CopyConstructible Fn     template<typename Fn>     struct wrapper<Fn, std::enable_if_t< std::is_copy_constructible<Fn>::value >>     {         Fn fn;          template<typename... Args>         auto operator()(Args&&... args) { return fn(std::forward<Args>(args)...); }     };      // specialization for MoveConstructible-only Fn     template<typename Fn>     struct wrapper<Fn, std::enable_if_t< !std::is_copy_constructible<Fn>::value         && std::is_move_constructible<Fn>::value >>     {         Fn fn;          wrapper(Fn&& fn) : fn(std::forward<Fn>(fn)) { }          wrapper(wrapper&&) = default;         wrapper& operator=(wrapper&&) = default;          // these two functions are instantiated by std::function         // and are never called         wrapper(const wrapper& rhs) : fn(const_cast<Fn&&>(rhs.fn)) { throw 0; } // hack to initialize fn for non-DefaultContructible types         wrapper& operator=(wrapper&) { throw 0; }          template<typename... Args>         auto operator()(Args&&... args) { return fn(std::forward<Args>(args)...); }     };      using base = std::function<T>;  public:     unique_function() noexcept = default;     unique_function(std::nullptr_t) noexcept : base(nullptr) { }      template<typename Fn>     unique_function(Fn&& f) : base(wrapper<Fn>{ std::forward<Fn>(f) }) { }      unique_function(unique_function&&) = default;     unique_function& operator=(unique_function&&) = default;      unique_function& operator=(std::nullptr_t) { base::operator=(nullptr); return *this; }      template<typename Fn>     unique_function& operator=(Fn&& f)     { base::operator=(wrapper<Fn>{ std::forward<Fn>(f) }); return *this; }      using base::operator(); };  using std::cout; using std::endl;  struct move_only {     move_only(std::size_t) { }      move_only(move_only&&) = default;     move_only& operator=(move_only&&) = default;      move_only(move_only const&) = delete;     move_only& operator=(move_only const&) = delete;      void operator()() { cout << "move_only" << endl; } };  int main() {     using fn = unique_function<void()>;      fn f0;     fn f1 { nullptr };     fn f2 { [](){ cout << "f2" << endl; } }; f2();     fn f3 { move_only(42) }; f3();     fn f4 { std::move(f2) }; f4();      f0 = std::move(f3); f0();     f0 = nullptr;     f2 = [](){ cout << "new f2" << endl; }; f2();     f3 = move_only(69); f3();      return 0; } 

Working version to coliru.

like image 31
Innocent Bystander Avatar answered Sep 24 '22 21:09

Innocent Bystander