C++ std::result_of和std::invoke_result

在 C++ 中,有时我们需要获取函数或可调用对象的返回值类型,以便进行后续的操作,在泛型编程中很常用,特别是当不同的参数集的结果类型不同时。

在早期的 C++ 版本中,我们需要手动推导函数返回值类型,这个过程非常复杂,也容易出错。为了解决这个问题,C++11 引入了 std::result_ofstd::result_of_t(C++14),这两个模板可以方便地获取函数或可调用对象的返回值类型。而在 C++17 中,废弃了 std::result_of 而引入了更好用的 std::invoke_resultstd::invoke_result_t

一、result_of

std::result_of是一个函数类型萃取器,它可以推导函数类型的返回值类型,它定义在头文件 <type_traits> 中。std::result_of 模板类需要两个模板参数:第一个是函数类型,第二个是函数的参数类型。它的定义如下:

1
2
template <typename F, typename... Args>
class result_of<F(Args...)>;

在模板参数中,F 必须是可调用类型、对函数的引用或对可调用类型的引用,Args 代表函数参数类型。例如,如果我们有一个函数 add,它的类型为 int(int, double),我们可以按照下列示例代码来使用 std::result_of 以获取其返回值类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <type_traits>
#include <functional>

int add(int x, double y)
{
return x + static_cast<int>(y);
}

int main()
{
// 也可以写作 std::result_of_t<decltype(&add)(int, double)>::type
std::result_of<std::function<int(int, double)>(int, double)>::type result = 0;
static_assert(std::is_same<decltype(result), int>::value, "result type should be int");
return 0;
}

C++14 引入了一个方便的类型别名 std::result_of_t,它可以替代 std::result_of::type,简化代码。

注意这里用的是 &add,而不是 add,F 不能识别函数类型,但可以是函数指针的类型。而代码中的 result 是一个变量,而不是类型,所以不能直接在 std::is_same 中引用,即不能写成 std::is_same<result, int>,必须使用 decltype 获取 result 的类型。

std::result_of 为什么被废弃

std::result_of 在设计上存在一些严重的缺陷和复杂性:

  1. 无法处理所有“可调用”类型std::result_of 对“可调用对象”的定义与 C++ 的 std::invoke 逻辑不完全一致。特别是对于成员函数指针或成员变量指针,std::result_of 的语义有时会产生歧义
  2. 语法笨重:它强制要求使用函数签名式语法 F(Args...),这在处理复杂的参数包转发时非常不便
  3. 不一致性std::invoke 在 C++17 统一了所有的调用方式(函数、成员函数、仿函数)。为了保持一致,标准委员会引入了 std::invoke_result 来完美匹配 std::invoke 的行为

二、invoke_result

在 C++17 中,std::result_of 已被弃用,建议使用 std::invoke_result 来代替,并在 C++20 中被正式移除。std::invoke_result 可以获取函数、成员函数和可调用对象的返回值类型。与 std::result_of 不同的是,std::invoke_result 支持成员函数指针和指向成员函数的指针,以及可调用对象的包装器 std::function

定义如下:

1
2
3
4
5
template <typename F, typename... Args>
struct invoke_result;

template <typename F, typename... Args>
using invoke_result_t = typename invoke_result<F, Args...>::type;

在模板参数中,F代表函数类型、成员函数指针类型或可调用对象类型,Args 代表函数或成员函数的参数类型。例如,如果我们有一个函数 add,它的类型为 int(int, double),我们可以参照如下示例代码使用 std::invoke_result_t 来获取它的返回值类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <type_traits>

int add(int x, double y)
{
return x + static_cast<int>(y);
}

// 获取 add 函数的返回类型
using ResultType = std::invoke_result_t<decltype(add), int, double>; // ResultType 是 int

// 配合模板使用
template <typename F, typename... Args>
auto execute(F&& f, Args&&... args) -> std::invoke_result_t<F, Args...> {
return f(std::forward<Args>(args)...);
}

int main()
{
std::invoke_result_t<decltype(add), int, double> result = 0;
static_assert(std::is_same<decltype(result), int>::value, "result type should be int");
return 0;
}

如果我们有一个类A和一个成员函数A::add(),则可以使用下列代码获取成员函数的返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <type_traits>

class A
{
public:
int add(int x, double y)
{
return x + static_cast<int>(y);
}
};

int main()
{
std::invoke_result_t<decltype(&A::add), A*, int, double> result = 0;
static_assert(std::is_same<decltype(result), int>::value, "result type should be int");
return 0;
}

如果我们有一个可调用对象,我们可以直接将它传递给std::invoke_result_t。例如:

1
2
3
4
5
6
7
8
9
10
11
12
#include <type_traits>
#include <functional>

int main()
{
std::function<int(int, double)> add = [](int x, double y) {
return x + static_cast<int>(y);
};
std::invoke_result_t<decltype(add), int, double> result = 0;
static_assert(std::is_same<decltype(result), int>::value, "result type should be int");
return 0;
}

我们直接将可调用对象 add 传递给 std::invoke_result 即可。

需要注意的是,如果函数、成员函数或可调用对象不接受指定的参数类型,则编译时将会出现错误。


C++ std::result_of和std::invoke_result
http://example.com/2026/05/18/C++-std-result-of和std-invoke-result/
作者
Yu xin
发布于
2026年5月18日
许可协议