C++自定义async实现报错:无匹配__invoke_r函数调用
问题:自定义async实现中std::packaged_task构造时的编译错误及返回值推导优化
我用简化代码实现了基于自定义async的http_GET函数,这个async接收回调参数,创建临时entity对象,可通过get_future()获取与回调返回值匹配的std::future
代码
#include <type_traits> #include <concepts> #include <memory_resource> #include <functional> #include <tuple> #include <cstdio> #include <utility> #include <type_traits> #include <future> using allocator_t = std::pmr::polymorphic_allocator<std::byte>; template <typename R> struct entity { template <typename Cb> requires (std::constructible_from<std::packaged_task<R()>, Cb>) entity(Cb&& fn, allocator_t allocator = {}) : task_{ std::move(fn) } { } auto get_future() -> std::future<R> { return std::future<R>{}; } std::packaged_task<R()> task_; }; template <typename Cb, typename... Args, std::size_t... I> auto async_impl_alloc(Cb&& fn, std::tuple<Args...> tuple, std::index_sequence<I...>) -> std::future<decltype(fn(std::get<I>(std::move(tuple))...))> { return entity<decltype(fn(std::get<I>(std::move(tuple))...))>([fn = std::forward<Cb>(fn), ... bargs = std::get<I>(std::move(tuple))]() mutable { fn(std::forward<decltype(bargs)>(bargs)...); }, std::get<std::index_sequence<I...>::size()>(std::move(tuple))).get_future(); } template <typename Cb, typename... Args, std::size_t... I> auto async_impl_noalloc(Cb&& fn, std::tuple<Args...> tuple, std::index_sequence<I...>) -> std::future<decltype(fn(std::get<I>(std::move(tuple))...))> { return entity<decltype(fn(std::get<I>(std::move(tuple))...))>([fn = std::forward<Cb>(fn), ... bargs = std::get<I>(std::move(tuple))]() mutable { fn(std::forward<decltype(bargs)>(bargs)...); }).get_future(); } template<typename Cb, typename... Args> auto async2(Cb&& fn, Args&&... args) { auto tuple = std::forward_as_tuple(std::forward<Args>(args)...); constexpr std::size_t cnt = sizeof...(Args); if constexpr (std::same_as<std::remove_cvref_t<std::tuple_element<cnt-1, decltype(tuple)>>, allocator_t>) { printf("with allocator\n"); return async_impl_alloc(std::forward<Cb>(fn), std::forward<decltype(tuple)>(tuple), std::make_index_sequence<cnt-1>{}); } else { printf("without allocator\n"); return async_impl_noalloc(std::forward<Cb>(fn), std::forward<decltype(tuple)>(tuple), std::make_index_sequence<cnt>{}); } } struct response {}; auto http_GET(std::pmr::string url) { return async2([](const std::pmr::string& url){ return response{}; }, std::move(url)); } int main() { allocator_t allocator; auto fut = http_GET("http://my.url.com"); }
错误信息
/opt/compiler-explorer/gcc-trunk-20230123/include/c++/13.0.1/future:1491:41: error: no matching function for call to '__invoke_r<response>(async_impl_noalloc<http_GET(std::pmr::string)::<lambda(const std::pmr::string&)>, std::__cxx11::basic_string<char, std::char_traits<char>, std::pmr::polymorphic_allocator<char> >&&, 0>(http_GET(std::pmr::string)::<lambda(const std::pmr::string&)>&&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::pmr::polymorphic_allocator<char> >&&>, std::index_sequence<0>)::<lambda()>&)' 1491 | return std::__invoke_r<_Res>(_M_impl._M_fn, | ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~ 1492 | std::forward<_Args>(__args)...); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
解决方案
1. 编译错误原因及修复
错误根源在于传递给std::packaged_task<R()>的lambda表达式没有返回值:lambda内部调用了fn(...)但未将结果返回。std::packaged_task<R()>要求可调用对象必须返回R类型,而你的lambda实际返回void,导致__invoke_r无法找到匹配的重载(它需要将返回值转换为response,但lambda无返回值)。
修改lambda,添加return语句即可修复:
// 在async_impl_noalloc和async_impl_alloc中修改lambda [fn = std::forward<Cb>(fn), ... bargs = std::get<I>(std::move(tuple))]() mutable { return fn(std::forward<decltype(bargs)>(bargs)...); // 添加return }
2. 简化回调返回值推导的方法
当前代码用decltype(fn(std::get<I>(std::move(tuple))...))推导返回值,虽可行但可更简洁:
方法一:使用std::invoke_result_t
直接利用标准库类型 trait 推导可调用对象的返回值,代码更清晰:
template <typename Cb, typename... Args, std::size_t... I> auto async_impl_noalloc(Cb&& fn, std::tuple<Args...> tuple, std::index_sequence<I...>) -> std::future<std::invoke_result_t<Cb, Args...>> { using R = std::invoke_result_t<Cb, Args...>; return entity<R>([fn = std::forward<Cb>(fn), ... bargs = std::get<I>(std::move(tuple))]() mutable { return fn(std::forward<decltype(bargs)>(bargs)...); }).get_future(); }
方法二:让entity配合自动推导(C++20+)
简化entity的构造逻辑,结合std::invoke_result_t直接推导返回类型:
// 简化entity构造,移除冗余requires(packaged_task会自行检查兼容性) template <typename R> struct entity { template <typename Cb> entity(Cb&& fn, allocator_t allocator = {}) : task_{ std::move(fn) } {} // 修正:返回task关联的future而非空future std::future<R> get_future() { return task_.get_future(); } std::packaged_task<R()> task_; }; // 用auto推导返回值,配合std::invoke_result_t简化类型推导 template <typename Cb, typename... Args, std::size_t... I> auto async_impl_noalloc(Cb&& fn, std::tuple<Args...> tuple, std::index_sequence<I...>) { using R = std::invoke_result_t<Cb, std::tuple_element_t<I, std::tuple<Args...>>...>; return entity<R>([fn = std::forward<Cb>(fn), ... bargs = std::get<I>(std::move(tuple))]() mutable { return fn(std::forward<decltype(bargs)>(bargs)...); }).get_future(); }
额外注意点
原代码中entity::get_future()返回空的std::future<R>是错误的,应该返回task_.get_future(),否则future无法获取任务的执行结果。
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

