You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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()后就能正常使用)。需解答以下问题:

  1. std::bind_front的特殊之处是什么?
  2. 如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 15:15:30