目录

前言

本篇博文将详细介绍一下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

std::invoke_result的实现详解的更多相关文章

  1. std::thread线程库详解(2)

    目录 目录 简介 最基本的锁 std::mutex 使用 方法和属性 递归锁 std::recursive_mutex 共享锁 std::shared_mutex (C++17) 带超时的锁 总结 简 ...

  2. std::thread线程库详解(3)

    目录 目录 前言 lock_guard scoped_lock (C++17) unique_lock shared_lock 总结 ref 前言 前两篇的博文分别介绍了标准库里面的线程和锁,这一次的 ...

  3. std::thread线程库详解(4)

    目录 目录 前言 条件变量 一些需要注意的地方 总结 前言 本文主要介绍了多线程中的条件变量,条件变量在多线程同步中用的也比较多.我第一次接触到条件变量的时候是在完成一个多线程队列的时候.条件变量用在 ...

  4. std::thread线程库详解(5)

    目录 目录 前言 信号量 counting_semaphore latch与barrier latch barrier 总结 前言 前面四部分内容已经把目前常用的C++标准库中线程库的一些同步库介绍完 ...

  5. C++11 std::chrono库详解

    所谓的详解只不过是参考www.cplusplus.com的说明整理了一下,因为没发现别人有详细讲解. chrono是一个time library, 源于boost,现在已经是C++标准.话说今年似乎又 ...

  6. C++11 并发指南六(atomic 类型详解三 std::atomic (续))

    C++11 并发指南六( <atomic> 类型详解二 std::atomic ) 介绍了基本的原子类型 std::atomic 的用法,本节我会给大家介绍C++11 标准库中的 std: ...

  7. C++11 并发指南六( <atomic> 类型详解二 std::atomic )

    C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)  一文介绍了 C++11 中最简单的原子类型 std::atomic_flag,但是 std::atomic_flag ...

  8. C++11 并发指南五(std::condition_variable 详解)

    前面三讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解)>分别介绍了 std::thread,std::mut ...

  9. C++11 并发指南------std::thread 详解

    参考: https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter3-Thread/Int ...

随机推荐

  1. SQL Server management studio使用sa连接时报错与伺服器的连接已成功,但在登入程序是发生错误

    使用Sql Server management studio的sa用户连接数据库时,报如下错误 解决方法: 1.使用windows验证登录 2.右键点击连接,点击属性,点击安全性,选择混合验证 3.重 ...

  2. 本地Mac通过堡垒机代理实现跨堡垒机scp问题

    近日,公司在跳板机前架设了堡垒机,以防止ssh攻击,但这带来一个问题,我们平常直接ssh跳板机,可以直接使用scp来上传或下载跳板机数据到本地 架设堡垒之后经常使用的scp工具不好用了 于是本期就来解 ...

  3. ichartjs插件的使用

    项目中可能会用到饼状图.柱状图.环形图等,ichartjs是一个很不错的插件,体量小,只需引入ichart.1.2.1.min.js即可满足基础需求,github下载地址是:https://githu ...

  4. 免安装的tomcat转服务

    一:确保tomcat 在点击bin\startup 文件可以正常启动访问: 二:本机安装有JDK: 三:本机环境变量配置:JAVA_HOME:C:\Java\jdk1.7.0_17; 四:本机Tomc ...

  5. day03 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数

    本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...

  6. 指令集架构 x86-64 x86架构的64位拓展,向后兼容于16位及32位的x86架构

    https://zh.wikipedia.org/wiki/X86 x86泛指一系列英特尔公司用于开发处理器的指令集架构,这类处理器最早为1978年面市的"Intel 8086"C ...

  7. python 10函数式编程

                                                                               函数式编程 函数是Python内建支持的一种封装, ...

  8. Redis 实战 —— 10. 实现内容搜索、定向广告和职位搜索

    使用 Redis 进行搜索 P153 通过改变程序搜索数据的方式,并使用 Redis 来减少绝大部分基于单词或者关键字进行的内容搜索操作的执行时间. P154 基本搜索原理 P154 倒排索引 (in ...

  9. 使用ganglia 实现监控 hadoop 和 hbase(详细过程总结)

    一,环境准备 hadoop 2.8.2 分布式环境(三个节点 安装请参考 hadoop分布式环境安装) hbase 1.2.6  分布式环境(三个节点 ,安装参考hbase分布式环境安装 ) 主节点采 ...

  10. vmware打开虚拟级断电情况下,无法找到虚拟机文件

    1.此时会在建立的虚拟机目录下,有一些 %虚拟机名字%.vmx.lck 或者别的   %虚拟机名字%.***.lck   删除这些文件夹 2.虚拟文件 是一个后缀名为vmx的文件,发现断电后 变成了v ...