使用std::bind_front绑定仅移动参数构造std::packaged_task编译失败
尝试用std::bind_front将仅移动类型MoveOnly的对象绑定为函数foo的第一个参数,再用生成的函数对象构造std::packaged_task,代码如下:
#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))); }
使用GCC 12.1.0通过命令g++ -std=c++20 repro.cpp -o repro.exe编译时,出现如下错误:
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:
仅使用std::bind_front时会触发该错误,但其他仅移动可调用对象可以正常构造std::packaged_task(比如给MoveOnly添加operator()后就能正常使用)。需解答以下问题:
std::bind_front的特殊之处是什么?- 如何让
std::bind_front生成的函数对象适配std::packaged_task?
一、std::bind_front的特殊之处
std::bind_front生成的绑定对象,其左值调用运算符会被禁用——当绑定的参数是仅移动类型时,该对象只能通过右值(&&)调用。
原因在于,std::bind_front保存的仅移动参数只能被移动一次,如果允许左值调用(通过&引用调用),可能导致多次尝试移动同一个仅移动对象,触发未定义行为。因此标准库设计时,仅允许通过移动绑定对象本身(右值方式),来触发内部参数的移动。
而std::packaged_task执行任务时,是通过左值引用调用存储的可调用对象的(它需要保留对象状态,不允许移动)。这就导致了冲突:packaged_task尝试以左值方式调用bind_front生成的对象,但该对象的左值调用运算符不可用,最终触发编译错误。
二、解决方法:让bind_front对象适配packaged_task
有两种实用的解决方案:
1. 用std::move转移bind_front对象的所有权
通过std::move将bind_front生成的右值对象传入packaged_task,执行任务时再转移packaged_task的所有权,确保内部绑定对象能以右值方式被调用:
#include <future> #include <functional> #include <thread> #include <iostream> 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[]) { auto bound = std::bind_front(foo, MoveOnly(3)); std::packaged_task<int()> task(std::move(bound)); auto fut = task.get_future(); std::thread t(std::move(task)); t.join(); std::cout << fut.get() << std::endl; // 输出3 }
2. 用lambda表达式替代std::bind_front
lambda表达式的灵活性更高,可显式控制调用方式。通过移动捕获仅移动对象,并添加mutable关键字允许修改捕获变量(实现移动):
#include <future> #include <functional> #include <thread> #include <iostream> 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([m = MoveOnly(3)]() mutable { return foo(std::move(m)); }); auto fut = task.get_future(); std::thread t(std::move(task)); t.join(); std::cout << fut.get() << std::endl; // 输出3 }
lambda的左值调用运算符可用,完全适配std::packaged_task的调用逻辑。
内容的提问来源于stack exchange,提问作者Jeffrey Bosboom

