c++c++20packaged-taskbind-front

Constructing std::packaged_task from std::bind_front with a move-only bound argument does not compile


The following code uses std::bind_front to bind an object of move-only type as the first argument to a function, then constructs a std::packaged_task from the resulting function object. (Try it on Godbolt.)

#include <future>
#include <functional>

struct MoveOnly {
    int v;
    MoveOnly(int v) : v(v) {}
    MoveOnly(const MoveOnly&) = delete;
    MoveOnly& operator=(const MoveOnly&) = delete;
    MoveOnly(MoveOnly&&) = default;
    MoveOnly& operator=(MoveOnly&&) = default;
};

int foo(MoveOnly m) {
    return m.v;
}

int main(int argc, char* argv[]) {
    std::packaged_task<int()> task(std::bind_front(foo, MoveOnly(3)));
}

This code does not compile with GCC 12.1.0 using g++ -std=c++20 repro.cpp -o repro.exe. I get the following error message.

In file included from repro.cpp:1:
/usr/include/c++/12.1.0/future: In instantiation of ‘void std::__future_base::_Task_state<_Fn, _Alloc, _Res(_Args ...)>::_M_run(_Args&& ...) [with _Fn = std::_Bind_front<int (*)(MoveOnly), MoveOnly>; _Alloc = std::allocator<int>; _Res = int; _Args = {}]’:
/usr/include/c++/12.1.0/future:1466:7:   required from here
/usr/include/c++/12.1.0/future:1469:41: error: no matching function for call to ‘__invoke_r<int>(std::_Bind_front<int (*)(MoveOnly), MoveOnly>&)’
 1469 |             return std::__invoke_r<_Res>(_M_impl._M_fn,
      |                    ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
 1470 |                                          std::forward<_Args>(__args)...);
      |                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/12.1.0/tuple:41,
                 from /usr/include/c++/12.1.0/mutex:38,
                 from /usr/include/c++/12.1.0/future:38:
/usr/include/c++/12.1.0/bits/invoke.h:104:5: note: candidate: ‘template<class _Res, class _Callable, class ... _Args> constexpr std::enable_if_t<is_invocable_r_v<_Res, _Callable, _Args ...>, _Res> std::__invoke_r(_Callable&&, _Args&& ...)’
  104 |     __invoke_r(_Callable&& __fn, _Args&&... __args)
      |     ^~~~~~~~~~
/usr/include/c++/12.1.0/bits/invoke.h:104:5: note:   template argument deduction/substitution failed:
In file included from /usr/include/c++/12.1.0/bits/stl_pair.h:60,
                 from /usr/include/c++/12.1.0/tuple:38:
/usr/include/c++/12.1.0/type_traits: In substitution of ‘template<bool _Cond, class _Tp> using enable_if_t = typename std::enable_if::type [with bool _Cond = false; _Tp = int]’:
/usr/include/c++/12.1.0/bits/invoke.h:104:5:   required by substitution of ‘template<class _Res, class _Callable, class ... _Args> constexpr std::enable_if_t<is_invocable_r_v<_Res, _Callable, _Args ...>, _Res> std::__invoke_r(_Callable&&, _Args&& ...) [with _Res = int; _Callable = std::_Bind_front<int (*)(MoveOnly), MoveOnly>&; _Args = {}]’
/usr/include/c++/12.1.0/future:1469:34:   required from ‘void std::__future_base::_Task_state<_Fn, _Alloc, _Res(_Args ...)>::_M_run(_Args&& ...) [with _Fn = std::_Bind_front<int (*)(MoveOnly), MoveOnly>; _Alloc = std::allocator<int>; _Res = int; _Args = {}]’
/usr/include/c++/12.1.0/future:1466:7:   required from here
/usr/include/c++/12.1.0/type_traits:2614:11: error: no type named ‘type’ in ‘struct std::enable_if<false, int>’
 2614 |     using enable_if_t = typename enable_if<_Cond, _Tp>::type;
      |           ^~~~~~~~~~~
/usr/include/c++/12.1.0/future: In instantiation of ‘void std::__future_base::_Task_state<_Fn, _Alloc, _Res(_Args ...)>::_M_run_delayed(_Args&& ..., std::weak_ptr<std::__future_base::_State_baseV2>) [with _Fn = std::_Bind_front<int (*)(MoveOnly), MoveOnly>; _Alloc = std::allocator<int>; _Res = int; _Args = {}]’:
/usr/include/c++/12.1.0/future:1476:7:   required from here
/usr/include/c++/12.1.0/future:1479:41: error: no matching function for call to ‘__invoke_r<int>(std::_Bind_front<int (*)(MoveOnly), MoveOnly>&)’
 1479 |             return std::__invoke_r<_Res>(_M_impl._M_fn,
      |                    ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
 1480 |                                          std::forward<_Args>(__args)...);
      |                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/12.1.0/bits/invoke.h:104:5: note: candidate: ‘template<class _Res, class _Callable, class ... _Args> constexpr std::enable_if_t<is_invocable_r_v<_Res, _Callable, _Args ...>, _Res> std::__invoke_r(_Callable&&, _Args&& ...)’
  104 |     __invoke_r(_Callable&& __fn, _Args&&... __args)
      |     ^~~~~~~~~~
/usr/include/c++/12.1.0/bits/invoke.h:104:5: note:   template argument deduction/substitution failed:

I only get this error using std::bind_front. std::packaged_task accepts other move-only callables without any problem, and I am using this in my actual code (which queues tasks to be executed in a thread pool). For example, if you add an operator() to MoveOnly, you can construct a task from a MoveOnly object.

What makes std::bind_front different and how can I adapt the function objects from std::bind_front to work in std::packaged_tasks?


Solution

  • bind_front itself stores a copy of MoveOnly.

    Since MoveOnly can't be copied, we need to use std::move to move it to foo, which makes only rvalue ref qualifier operator() && valid in bind_front 's returning function wrapper:

    auto f = std::bind_front(foo, MoveOnly(3));
    f(); // not ok
    std::move(f)(); // ok
    

    And because packaged_task always invokes the underlying function through an lvalue, so compile fails.

    Instead of using bind_front, you can use a mutable lambda, which can be invoked by lvalue

    auto bind_front = [m = MoveOnly(3)] mutable { return foo(std::move(m)); };
    std::packaged_task<int()> task(std::move(bind_front));