C++20中如何正确接受模板化可调用对象?
解决方案:让可调用参数自动推导类型
核心问题在于你把std::function直接作为函数模板的参数类型,导致编译器无法将lambda等闭包类型隐式转换后参与模板推导。C++20下可以通过以下方式彻底解决,无需手动指定任何模板参数:
1. 重构run函数:接受任意可调用类型
放弃将std::function作为参数的强制要求,改用通用模板参数接收任意可调用对象,内部按需转换为std::function(如果需要类型擦除或存储)。利用C++20的std::invocable概念约束和std::invoke_result_t推导返回类型。
示例代码
首先假设你的Result类型定义如下(适配错误处理逻辑):
template <typename T> struct Result { bool is_ok; T value; static Result<T> ok(T val) { return {true, std::move(val)}; } static Result<T> error() { return {false, {}}; } };
重构后的通用run函数:
#include <functional> #include <concepts> #include <utility> // 适配任意可调用对象和参数列表的通用版本 template <typename F, typename... Args> requires std::invocable<F, Args...> auto run(F f, Args&&... args) -> Result<std::invoke_result_t<F, Args...>> { // 仅当内部需要类型擦除时,才转换为std::function using ReturnType = std::invoke_result_t<F, Args...>; using FuncType = std::function<ReturnType(Args...)>; FuncType func = std::move(f); // 示例重试逻辑:尝试3次调用 for (int retry = 0; retry < 3; ++retry) { auto result = func(std::forward<Args>(args)...); // 这里替换为你的成功判断逻辑 if (true) { return Result<ReturnType>::ok(std::move(result)); } } return Result<ReturnType>::error(); }
2. 调整runWithRetry包装函数
直接转发参数给run即可,编译器会自动推导所有模板参数:
template <typename F, typename... Args> auto runWithRetry(F f, Args&&... args) { // 可在此添加额外逻辑(如日志、前置检查),再调用run return run(std::forward<F>(f), std::forward<Args>(args)...); }
3. 调用示例(无需手动指定模板参数)
int main() { // 无参lambda调用 auto res1 = run([]{ return 42; }); auto res2 = runWithRetry([]{ return std::string("操作成功"); }); // 带参lambda调用,自动推导参数类型 auto res3 = run([](int x) { return x * 2; }, 10); auto res4 = runWithRetry([](double d) { return d + 3.14; }, 2.71); }
原理说明
- 原来的写法中,
std::function<T()>作为模板参数时,编译器无法将lambda的闭包类型隐式转换为std::function来完成推导(隐式转换发生在模板推导之后),导致必须手动指定T。 - 重构后,模板参数
F直接推导为lambda的闭包类型,std::invoke_result_t<F, Args...>自动推导返回值类型,彻底避免手动指定模板参数的需求。 - 若内部逻辑需要
std::function的类型擦除能力,仅在函数内部完成转换即可,不影响外部调用的简洁性。
内容的提问来源于stack exchange,提问作者Hesky Fisher
相关产品推荐
相关产品推荐

