结论:应使用 std::invoke_result_t 替代已废弃的 std::result_of;它以可调用类型 F 和参数类型包 ArgTypes... 为模板参数,严格模拟 INVOKE 表达式行为,支持成员指针、lambda 等复杂场景,但需配合 std::is_invocable_v 使用以防误推导。

直接说结论:用 std::invoke_result_t 替代已废弃的 std::result_of,是 C++17 起获取可调用对象返回类型的唯一推荐方式;它不依赖函数签名字符串,能正确处理成员指针、引用、右值等真实调用场景。
std::invoke_result_t 的模板参数怎么写?
它接受两个参数:F(可调用对象类型)和 ArgTypes...(参数类型包),顺序必须严格对应真实调用时的参数列表。
-
F必须是类型,不能是值 —— 所以要用decltype(f),而不是f本身 -
ArgTypes...是类型列表,不是实参值 —— 写int, double,不是1, 2.0 - 如果函数接受左值引用参数(如
void f(std::string&)),ArgTypes应写std::string&,而非std::string;否则推导失败 - 顶层 const/volatile 会被自动剥离,
const int和int在这里等价
为什么 std::result_of::type 会编译失败?
这是最常见误用:把函数类型字面量当模板参数传给 std::result_of。C++17 起该写法已被移除,且即使在旧标准下也极易出错。
-
std::result_of要求::type int(int, double)是一个完整函数类型,但实际中你拿到的往往是函数指针或重载集,编译器无法匹配 -
std::invoke_result_t不要求你“猜”签名,而是基于真实可调用对象类型 + 实际参数类型做 SFINAE 推导,更鲁棒 - 例如:对重载函数
void foo(int)和void foo(double),decltype(foo)是不明确的,但std::invoke_result_t可成功推导出第一个重载的返回类型
处理成员函数指针和 lambda 时要注意什么?
std::invoke_result_t 原生支持成员指针和捕获型 lambda,但参数顺序和对象绑定方式容易搞错。
- 成员函数指针需显式提供
this类型:对于struct S { int f(double); };,要写std::invoke_result_t或S*, double - lambda 类型是独有未命名类,必须用
decltype获取;带捕获的 lambda 不能转成函数指针,但std::invoke_result_t照样能推导其operator()返回类型 - 注意:lambda 若含模板 operator()(如泛型 lambda),需明确指定模板实参,否则
decltype拿到的是闭包类型,invoke_result_t无法穿透到内部模板
std::invoke_result_t 与 std::invoke 的配合陷阱
很多人以为 std::invoke_result_t 就是 std::invoke(f, args...) 的返回类型 —— 大部分时候成立,但有例外。
- 若
F是 void 返回函数,std::invoke_result_t正确给出void;而std::invoke返回void,但不能用于 decltype 上下文(比如声明变量) - 若参数中含非复制/非移动类型(如
std::unique_ptr),&& std::invoke_result_t仍能推导,但std::invoke实际调用时可能因所有权转移失败而崩溃 - 最关键一点:
std::invoke_result_t只做类型推导,不检查可调用性;即使F根本不能接受那些ArgTypes,也可能静默推导出错误类型(取决于特化实现),建议搭配std::is_invocable_v一起用
真正难的不是记住语法,而是理解 std::invoke_result_t 推导的是「INVOKE 表达式」的返回类型 —— 它模拟的是 std::invoke 的行为,不是函数声明的行为。任何脱离真实调用上下文去“凭空猜类型”的做法,都会掉进模板元编程最深的坑里。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











