Move-only version of std::function
Asked Answered
T

3

70

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?

Tenpenny answered 15/8, 2014 at 16:55 Comment(16)
std::function is broken in several different ways... I think that's generally accepted, but very difficult to fix without breaking existing code.Algophobia
Hey. Thanks for the comment. Now that you mention it, it would be nice to hear some specific ways in which it is broken.Tenpenny
possible duplicate of Why the initializer of std::function has to be CopyConstructible?Lebbie
@Nevin. that question overlaps, but I am also asking whether the std library has or maybe plans to offer a non-copyable version of std::function that has a deleted copy constructor. It does explain partly why you cannot have std::function work in both cases, but not why there aren't two versions of std::function. The technical problem described in that answer does not address the fact that we don't need to declare a copy constructor.Tenpenny
@KerrekSB I don't think that particular aspect is broken. Since function performs type erasure, it would become a run-time problem whether or not that instance of function is copyable.Culpepper
The LEWG (Library Evolution Working Group of the C++ Committee) has had some internal discussions on it, but no papers have yet emerged.Lebbie
Well you can reuse std::function to make it work.. kind of (note that function_mo itself is move-only, so no exceptions of hack will be thrown).Culpepper
@orm: One of the big sticking points is that the function call operator is const, which the library requires to mean thread-safe. This makes it hard for people who want to use function<void()> as a generic callable thing in concurrent settings. Another aspect that's somewhat half-baked is the type-erased allocator support, I believe (esp. regarding fancy pointers); function is the only class in the library that has a type-erased allocator and is also copyable. (See N3916 for some aspects. N4041 is also interesting.)Algophobia
@KerrekSB Doesn't the const only means that the function call operator is thread safe for the std::function instance not for the underlying type erased callable ?Schuster
@Drax, the underlying type erased object is also member of the std::function. So, any underlying methods accessed by the external call operator need to be const as well.Tenpenny
@Tenpenny not sure about that, if that member is a pointer-like, it is the pointer that is const (you cannot modify the thing that allows the indirection) not the pointed object (you can modify the thing that your pointer allows you to access).Schuster
Ah, @Drax, you're right. I also don't quite follow how the const part makes it hard to use in concurrent settings. If anything, it sounds like it should make it easier to use.Tenpenny
@Culpepper FYI your function_mo example blows up when initialized with a lambda: coliru.stacked-crooked.com/a/d9eea20f67d6e578 (or any non-DefaultConstructible type)Malacca
There is a proposal for std::any_invocable (sometimes called unique_function) that has been approved but won’t make C++20.Circuitry
That proposal has been renamed to std::move_only_function and is in the current draft of C++23.Ambrosius
You can use this github.com/ofats/any_invocableHubris
G
19

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

Generable answered 20/8, 2014 at 15:41 Comment(4)
Your suggestion to use the "impossibly fast delegates" code is a red herring: the essential trick it uses to speed things up is specific to method pointers (and from the comments it looks like it doesn't actually speed things up anyway). As written, it won't work on user-defined lambdas (or other functors), which is what the original question was asking about. Your example code seems to be more like a conventional std::function implementation.Taproom
@arthur The delegates code can be easily modified to consume operator() implicitly (ignoring template cases) in my experience. I do not know if it still gives a performance boost; it has been a long time since I used it in other than legacy situations (it is too much of a pain to maintain).Generable
What do you mean by "consume operator()"? Do you mean consume arbitrary classes that have an operator() (including lambda functions)? If so, that is most of the effort of writing a type-erased functor. Whether you consider it difficult or easy, there's no much point linking to something that doesn't do it (especially something that spends extra effort doing something unrelated).Taproom
@arthur it is an example of zero allocation std function like; mine is more complete but allocates.Generable
A
13

Yes, there is a proposal for std::move_only_function in the current draft of C++23, adopted 2021-10:

This paper proposes a conservative, move-only equivalent of std::function.

See also the cppreference entry on std::move_only_function:

Class template std::move_only_function is a general-purpose polymorphic function wrapper. std::move_only_function objects can store and invoke any constructible (not required to be move constructible) Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to member objects.
...
std::move_only_function satisfies the requirements of MoveConstructible and MoveAssignable, but does not satisfy CopyConstructible or CopyAssignable.

Ambrosius answered 10/11, 2021 at 19:31 Comment(0)
M
12

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.

Malacca answered 16/9, 2018 at 22:33 Comment(6)
public base allows conversion to const std :: function & and copying (with exception)Benenson
@Benenson Converting to std::function is so weird (?). In that way we can afterwards copy std::function<void()> ff = f3; auto fff = ff; How is that possible?Feather
This implementation is somewhat flawed: unique_function(Fn&& f) matches to much I think... fn a; fn b; a = b; compiles but should not...Feather
I simply disabled that template for unique_function<T> itself.Shields
Should remove the possibility of storing a reference to the callable unique_function(Fn&& f) : base(wrapper<std::remove_reference_t<Fn>>{ std::forward<std::remove_reference_t<Fn>>(f) }) { } and same for operator=Calabria
@RickyLung feel free to edit the answer. It's been a few years and it will take me a while to reorient myself to it.Malacca

© 2022 - 2024 — McMap. All rights reserved.