目录

前言

本篇博文将详细介绍一下libstdc++std::invoke_result的实现过程,由于个人水平不足,可能最终的实现过程略有误差,还请各位指正。

invoke_result

std::invoke_result是C++17标准库里面的一个接口,它可以返回任何可调用的返回值,比如函数,函数对象,对象方法等。它的接口为[1]

template< class F, class... ArgTypes>
class invoke_result;

在C++17之前,std::invoke_result有一个类似功能的模板,名字为std::result_of,他们之间的差别只有在接口上有略微的差别,

template< class >
class result_of; // not defined template< class F, class... ArgTypes >
class result_of<F(ArgTypes...)>;

std::result_of于C++17标记为deprecated,并在C++20中被移除。

标准库中的invoke_result

我们首先看看一下标准库中std::invoke_result的实现,了解一下其大致的结构。

graph TD;
invoke_result[std::invoke_result];
result_of[std::result_of];
__invoke_result[std::__invoke_result];
invoke_result --> __invoke_result;
result_of --> __invoke_result;

__result_of_impl[std::__result_of_impl];
__invoke_result --> __result_of_impl;

__result_of_memobj[std::__result_of_memobj];
__result_of_memfun[std::__result_of_memfun];
__result_of_other_impl[std::__result_of_other_impl];

__result_of_impl --> __result_of_memobj;
__result_of_impl --> __result_of_memfun;
__result_of_impl --> __result_of_other_impl;

__S_test["__S_test()"];
__result_of_other_impl --> __S_test;

__result_of_memobj_ref[std::__result_of_memobj_ref];
__result_of_memobj_deref[std::__result_of_memobj_deref];
__result_of_memobj --> __result_of_memobj_ref;
__result_of_memobj --> __result_of_memobj_deref;

__result_of_memfun_ref[std::__result_of_memfun_deref];
__result_of_memfun_deref[std::__result_of_memfun_deref];
__result_of_memfun --> __result_of_memfun_ref;
__result_of_memfun --> __result_of_memfun_deref;

__result_of_memfun_ref_impl[std::__result_of_memfun_ref_impl];
__result_of_memfun_deref_impl[std::__result_of_memfun_deref_impl];
__result_of_memobj_ref_impl[std::__result_of_memobj_ref_impl];
__result_of_memobj_deref_impl[std::__result_of_memobj_deref_impl];

__result_of_memfun_ref --> __result_of_memfun_ref_impl;
__result_of_memfun_deref --> __result_of_memfun_deref_impl;
__result_of_memobj_ref --> __result_of_memobj_ref_impl;
__result_of_memobj_deref --> __result_of_memobj_deref_impl;

__result_of_memfun_ref_impl --> __S_test;
__result_of_memfun_deref_impl --> __S_test;
__result_of_memobj_ref_impl --> __S_test;
__result_of_memobj_deref_impl --> __S_test;

可以看到,整个实现有两层判断(有两个判断是类似的,可以看作是一层),其中第一个层位于std::__result_of_impl,用来判断不同的调用方法,大致分为三种:

  1. 对象方法,比如以下代码中的struct Ainvoke_mem_fun,调用方法为obj.fun(args...)
struct A {
int invoke_mem_fun();
};
  1. 对象成员,比如以下代码中的struct B中的invoke_mem_obj,调用方法为obj.fun(其实就是一个简单的引用成员)。
struct B {
int invoke_mem_obj;
};
  1. 其他,比如以下代码中的invoke_other,其调用方法为fun(args...),这一部分包括了函数对象和普通函数。
int invoke_other();

那么std::__result_of_impl是如何区分出了这三种不同的调用方式呢?

libstdc++中实现了两个类型判断,is_member_object_pointeris_member_function_pointer。通过这两个判断不同的输出,来区分三种不同的调用方式。

这两个函数的结构差不多,首先会对判断输入的类型是否为对象的成员,判断方法为进行类似下面代码的模式匹配

template<typename>
struct __mem_and_obj_type: public std::__failure_type{}; template<typename _Tp, typename _Cp>
struct __mem_and_obj_type<_Tp _Cp::*> {
typedef _Tp mem_type;
typedef _Cp obj_type;
};

这一部分代码是从我实现的部分截取出来的,其中最主要的部分就是_Tp _Cp::*,他分开成员类型和对象类型,如果能分开则为对象的成员。

第二步则是判断上一步分离得到的成员类型是否为函数类型,如果是函数类型,那么其为对象方法,否则为对象成员。

由此,通过那两个类型的判断,可以有三种不同的结果,true,falsefalse,truefalse,false,不同的结果对应着不同的调用方法。看到这里,肯定会有人思考,有没有可能结果都是true呢?

首先从libstdc++实现上这是不可能的,因为在实现第二步的时候,is_member_object_pointer几乎是直接对is_member_function_pointer取反的(实现上取反)。所以,不可能同时出现两个都为true的情况。

而实际中,却的确有可能出现这种情况(我也不知道是不是我那里弄错了,先写下来吧)

struct STest {
double operator() (int, double) {
std::cout << "Test" << std::endl;
return 0;
}
}; struct CTest {
struct STest s;
int s_m(int, double){};
};

这种情况下,如果我们想获得CTest::s的返回值,是有一个很奇怪的情况的,我们是把它当作函数呢,还是一个成员呢?如果从调用的情况来看,CTest::sCTest::s_m一样,都是obj.fun(args...),但是实际中,std::invoke_result会将它视为对象成员。所以如果我们使用std::invoke_result<decltype(&CTest::s), CTest, int, double>::type是会报错的,正确的方法是只能使用std::invoke_result<decltype(&CTest::s), CTest>::type。由于没有去查看文献,所以不清楚这一部分是bug还是feature或者是UB,有时间再详细考究一下吧。

好了,目前已经详细说完了std::invoke_result的第一层判断,接下来我们来看看第二层判断。

第二层判断有两个部分,我们只看其中的一个部分,因为实际上这两个部分都是一样的。

这一层的判断位于对象成员和对象方法部分。即区分std::__result_of_memfun_derefstd::__result_of_memfun_ref。这两个的不同之处在于,前者的对象为指针。区分的方法也很简单粗暴,直接判断是否参数里面的对象类型与上文中使用_Tp _Cp::*获得的对象类型_Cp是否一致或者为继承关系,如果为是,则使用std::__result_of_memfun_ref否则为std::__result_of_memfun_deref

通过上面两层的判断,已经成功的将不同的调用方法进行了分类,接下来就是使用decltype来获取返回值了。这一部分就很简单了,由上面的几次分类,分为了五种不同的调用(加上不同的对象)。

  1. __invoke_memfun_ref
(std::declval<_Tp1>().*std::declval<_Fp>())(std::declval<_Args>()...)
  1. __invoke_memfun_deref
((*std::declval<_Tp1>()).*std::declval<_Fp>())(std::declval<_Args>()...)

3. `__invoke_memobj_ref`

```C++
std::declval<_Tp1>().*std::declval<_Fp>()
  1. __invoke_memobj_deref
(*std::declval<_Tp1>()).*std::declval<_Fp>()
  1. __invoke_other
std::declval<_Fn>()(std::declval<_Args>()...)

但是比较好奇的是,程序没有直接通过上面的方法获得,而是构造了一个return_type _S_test(int),然后获取的。除了返回的类型外,std::invoke_result还保存了调用的类型,即之前提到的那五种,这一部分应该是为了实现std::invoke而保存的。

我的实现

为了学习std::invoke_result,我也实现了一个类似的ink::invoke_result。层次结构类似,不过将_S_test()删去了,改成了直接的实现。

github gist: ink_invoke_result.cpp

后记

阅读标准库里面的实现的确可以学到很多东西,即使以后不会写类似的代码,但是在使用的时候也会对C++有更加清晰的理解。同时也发现了C++目前的一个缺陷,在实现这种接口的时候,免不了要进行多层包装,而每一层的包装,标准库都是直接将其暴露在std命名空间中,这导致了在使用代码提示的时候极大的降低了提示的效率和美观(一大堆的以下划线开头的item真的非常非常非常难看,而且头大)。

博客原文: https://www.cnblogs.com/ink19/p/cpp_invoke_result.html


  1. std::result_of, std::invoke_result - cppreference.com

最新文章

  1. ★Kali信息收集★8.Nmap :端口扫描
  2. 洛谷P1111 修复公路
  3. C#中使用正则表达式验证电话号码、手机号、身份证号、数字和邮编
  4. NET-SNMP开发——日志输出
  5. 离线安装Cloudera Manager 5和CDH5(最新版5.1.3) 完全教程
  6. DDD 领域驱动设计-看我如何应对业务需求变化?
  7. ES mlockall作用——preventing that memory from being paged to the swap area
  8. C语言中字符串常量到底存在哪了?
  9. sessionFactory.getCurrentSession()的引出
  10. vs2012 密匙
  11. 【Machine Learning in Action --4】朴素贝叶斯过滤网站的恶意留言
  12. Centos6.5 mysql折腾记
  13. datatables 学习笔记1 基础篇
  14. (MonoGame从入门到放弃-1) MonoGame环境搭建
  15. 操作系统内核Hack:(一)实验环境搭建
  16. Intellij IDEA 插件开发之自建插件仓库
  17. 如果以一个树状的形式返回一个UIView的所有子视图
  18. golang 自定义json解析
  19. 在.net core中使用Thrift
  20. 强化学习之QLearning

热门文章

  1. 5V 升压 8.4V,5V 转 8.4V 做两节锂电池充电芯片
  2. 两节锂电池充电芯片,和保护IC的接法
  3. 注入器(injector)
  4. Java基础复习3
  5. Bitter.Core系列三:Bitter ORM NETCORE ORM 全网最粗暴简单易用高性能的 NETCore ORM 之 示例模型创建
  6. ubuntu14.04 LEMP(linux+nginx+mysql+php5)构建环境
  7. 前序遍历 排序 二叉搜索树 递归函数的数学定义 return 递归函数不能定义为内联函数 f(x0)由f(f(x0))决定
  8. 成为一名优秀的Java程序员9+难以置信的公式
  9. (一)在Spring Boot应用启动之后立刻执行一段逻辑
  10. 七:SpringBoot-集成Redis数据库,实现缓存管理