C++ std::result_of和std::invoke_result
在 C++ 中,有时我们需要获取函数或可调用对象的返回值类型,以便进行后续的操作,在泛型编程中很常用,特别是当不同的参数集的结果类型不同时。
在早期的 C++ 版本中,我们需要手动推导函数返回值类型,这个过程非常复杂,也容易出错。为了解决这个问题,C++11 引入了 std::result_of 和 std::result_of_t(C++14),这两个模板可以方便地获取函数或可调用对象的返回值类型。而在 C++17 中,废弃了 std::result_of 而引入了更好用的 std::invoke_result 和 std::invoke_result_t。
一、result_of
std::result_of是一个函数类型萃取器,它可以推导函数类型的返回值类型,它定义在头文件 <type_traits> 中。std::result_of 模板类需要两个模板参数:第一个是函数类型,第二个是函数的参数类型。它的定义如下:
1 | |
在模板参数中,F 必须是可调用类型、对函数的引用或对可调用类型的引用,Args 代表函数参数类型。例如,如果我们有一个函数 add,它的类型为 int(int, double),我们可以按照下列示例代码来使用 std::result_of 以获取其返回值类型:
1 | |
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 在设计上存在一些严重的缺陷和复杂性:
- 无法处理所有“可调用”类型:
std::result_of对“可调用对象”的定义与 C++ 的std::invoke逻辑不完全一致。特别是对于成员函数指针或成员变量指针,std::result_of的语义有时会产生歧义 - 语法笨重:它强制要求使用函数签名式语法
F(Args...),这在处理复杂的参数包转发时非常不便 - 不一致性:
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 | |
在模板参数中,F代表函数类型、成员函数指针类型或可调用对象类型,Args 代表函数或成员函数的参数类型。例如,如果我们有一个函数 add,它的类型为 int(int, double),我们可以参照如下示例代码使用 std::invoke_result_t 来获取它的返回值类型:
1 | |
如果我们有一个类A和一个成员函数A::add(),则可以使用下列代码获取成员函数的返回值:
1 | |
如果我们有一个可调用对象,我们可以直接将它传递给std::invoke_result_t。例如:
1 | |
我们直接将可调用对象 add 传递给 std::invoke_result 即可。
需要注意的是,如果函数、成员函数或可调用对象不接受指定的参数类型,则编译时将会出现错误。