Move-Only Version of Std::Function

Why does C++23 std::move_only_function not have deduction guides?

Type-erasing wrappers like move_only_function are designed to be used on API boundaries, where the types are explicit, which makes CTAD for these of dubious usefulness.

Any CTAD for these callable wrappers would have to be quite limited anyway - it can't handle overloaded functions or function templates, which also means that it can't handle generic lambdas, which is a rather significant limitation on its usefulness. std::function's CTAD also comes with a disclaimer that later standards can change the deduced type (we haven't changed it yet, but we haven't removed the disclaimer either).

And with move_only_function it's not just the return and argument types that are deduced. const, noexcept, and ref-qualifiers are all in play, and that introduces new design issues. For instance, does deducing from int (*)(int) deduce int(int)? Why not int(int) const - function pointers are const-callable, after all?

And if CTAD turns out to be needed - and someone come up with a good design for it - it can always be added later.

I don't know if these points were all raised in the LEWG discussion (the minutes are rather sparse), but I think they are more than enough to justify not supporting CTAD.

Bind move-only structure to function

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.

Constructing a tuple from a move only type with a std::function member

This is apparently an implementation bug: there's no initializer list in your code at all, and current versions of all compilers accept it (MSVC since v19.22).

returning move only types from free function vs member function

Take a look at the following code. If the get method would move the string out of the member variable, what should be the value of t?

error e = make_error(std::string{"foobar"});
std::string s = e.get();
std::string t = e.get();

You can return a reference to the member variable. It might look as follows:

const T& get() const { return val_T_int; }

The problem with that solution is, that you can't use it with non-copyable types the following way:

std::unique_ptr<...> result = make_error(...).get();

However, you can use rvalue-references for this to solve this problem. If you declare get in the following way, the function can only be used on temporaries (and explicitly moved objects). So it's fine to move the member variable out of the object.

T get() && { return std::move(val_T_int); }

Moving a lambda: once you've move-captured a move-only type, how can the lambda be used?

You can move the lambda, that's fine. That's not what your problem is though, you're trying to instantiate a std::function with a noncopyable lambda. And the:

template< class F > 
function( F f );

constructor of function does:

5) Initializes the target with a copy of f.

This is because std::function:

satisfies the requirements of CopyConstructible and CopyAssignable.

Since function has to be copyable, everything you put into it must also be copyable. And a move-only lambda does not meet that requirement.

C++11 STL container that supports move-only types and has O(1) write-access to beginning and end

This is a classic example of why a [MCVE] is so useful.

std::function<void()> fun = std::move(queue.front());

The above won't compile with a non-copyable content in the queue. But the queue works fine. std::deque solves your problem.

std::function requires its contents to be copyable. Even if you never move it, it requires it be copyable. std::function uses type erasure, so "how to copy" the contents is stored when you store something in it.

Here is a move-only std::function that does not do the small buffer optimization I wrote on SO two years ago.

Today I would write it differently. I would split the type erasure from the storage, and write a separate SBO storage type, then join them together to write task<Sig>.

Amusingly, packaged_task<void(Args...)> is a type erased SBO move-only wrapper for packaged_task<R(Args...)>. But it does much more, so I would avoid using it.

Now, the rules for std containers with regards to the requirements for their content have varied, with the standard regularly getting more liberal. At one point a bunch of requirements where placed on types even if it wasn't used; the current standard states that these requirements are on a per-method basis. Many compilers enforced those more liberal requirements before the standard moved (because there was little need to be strict, the standard did not demand it), so even in compilers prior to the liberalization it wasn't a problem. I am uncertain if this liberalization occurred in std::deque by C++11 or not; this is, however, an example of "if it works in your compiler, use it, because future compilers are going to support it".

How to create an std::function from a move-capturing lambda expression?

template<class F> function(F f);

template <class F, class A> function(allocator_arg_t, const A& a, F f);

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

§20.9.11.2.1 [func.wrap.func.con]

Note that operator = is defined in terms of this constructor and swap, so the same restrictions apply:

template<class F> function& operator=(F&& f);

Effects: function(std::forward<F>(f)).swap(*this);

§20.9.11.2.1 [func.wrap.func.con]

So to answer your question: Yes, it is possible to construct a std::function from a move-capturing lambda (since this only specifies how the lambda captures), but it is not possible to construct a std::function from a move-only type (e.g. a move-capturing lambda which move-captures something that is not copy constructible).



Related Topics



Leave a reply



Submit