C++的std::result_of和std::invoke_result有什么替代关系? (C++17/20演变)

2次阅读

std::result_of 在 c++17 被弃用、C++20 移除,应统一替换为 SFINAE 安全且语义清晰的 std::invoke_result_t;后者严格遵循 std::invoke 规则处理参数、cv-qualifier 和引用类型,支持所有可调用对象,是标准唯一正统方案。

C++的std::result_of和std::invoke_result有什么替代关系? (C++17/20演变)

std::result_of 在 C++17 被弃用,直接换用 std::invoke_result

std::result_of 已在 C++17 中被标记为 deprecated,C++20 彻底移除。它不能用于 SFINAE 友好场景,且对 cv-qualifier 和引用类型推导不严谨 —— 比如 std::result_of 实际会尝试调用 F::operator()const 重载(若存在),行为不可靠。

替代方案只有一个:std::invoke_result,它基于 std::invoke 协议定义,语义更清晰、SFINAE 安全、支持所有可调用对象(函数指针成员函数指针、Lambda、functor)。

  • std::result_of → 改写为 std::invoke_result_t
  • 注意:std::invoke_result类模板,需用 _t 别名(如 std::invoke_result_t)获取类型,而非 ::type
  • 旧写法 typename std::result_of::type 在 C++17 编译器上会触发 -Wdeprecated-declarations 警告

std::invoke_result_t 的参数传递方式影响推导结果

std::result_of 不同,std::invoke_result 严格按 std::invoke 规则处理参数:左值被转发为左值引用,右值被移动,cv-qualifier 保留。这意味着传参形式直接决定能否匹配目标可调用对象。

  • 若函数接受 int&&,传 std::move(x) 才能成功推导;传 x(左值)会失败
  • 若 functor 的 operator() 是 const 限定的,std::invoke_result_t 合法,但 std::invoke_result_t 可能失败
  • 成员函数指针需显式写出对象类型:如 std::invoke_result_t,不能省略 S

常见错误现象:std::invoke_result_t 编译失败,但 func 明明能用 —— 很可能因为 func 是左值,而它的 operator() 非 const,此时应改用 std::invoke_result_t

立即学习C++免费学习笔记(深入)”;

从 C++14 迁移到 C++17+ 时容易漏掉的细节

很多老代码用 std::result_of 做 traits 判断,升级编译器后没报错但逻辑出偏,问题往往藏在类型别名嵌套或模板特化里。

  • 宏定义中隐藏的 std::result_of(如某些 Boost 兼容层)需手动替换,否则 C++20 下直接编译不过
  • 自定义 traits 类模板若继承std::result_of<...>,必须重写为继承 std::invoke_result<...> 并用 ::type 提取(std::invoke_result 本身无 ::type,得用 typename std::invoke_result<...>::type
  • Clang 和 GCC 对 std::result_of 的弃用警告默认不开启(如 GCC 需加 -Wdeprecated-declarations),容易错过迁移时机

为什么不用 std::declval + decltype 模拟?

有人试图用 decltype(std::declval()(std::declval()...)) 替代,看似等价,但有严重缺陷:

  • 不支持成员函数指针调用语法(obj.*ptr(obj.*ptr)() 无法在 unevaluated context 中合法写出)
  • 重载函数名(非指针)无法解析,std::declval() 无法绑定到具体重载
  • 不处理 std::reference_wrapper 自动解引用逻辑,而 std::invoke 协议明确支持
  • 性能上无差异,但可维护性差 —— std::invoke_result 是标准规定的唯一正统方案

真正复杂的地方在于:不是“能不能推导出返回类型”,而是“是否以和实际调用一致的方式推导”。只要涉及成员指针、refwrap、重载集,手写 decltype 就大概率漏 case。标准库替你把 invoke 的所有分支都覆盖了,别自己造轮子。

text=ZqhQzanResources