std::invoke解析

初识std::invoke

        std::invoke是c++17标准库引入的一个函数模板。这个函数模板能做什么?原理是什么?先来看一个简单的例子,回答std::invoke“能做什么”。

#include <functional>
#include <iostream>
#include <algorithm>
#include <vector>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

struct calc {
    int add(int a, int b) { return a + b; }
    int sub(int a, int b) { return a - b; }
};

int main(int argc, char **argv)
{
    int _add = std::invoke(add, 4, 5);
    int _sub = std::invoke(sub, 7, 3);
    calc cobj;
    _add = std::invoke(&calc::add, cobj, 40, 5);
    _sub = std::invoke(&calc::sub, cobj, 70, 3);
    
    return 0;
}

        通过上面的例子,可以看出,std::invoke仅仅是对函数指针的调用做了一下封装,这种技术被称作委托。单从上面的例子来看,std::invoke的存在没有任何意义:直接调用函数更加方便,效率也更高,而通过std::invoke调用并不能带来任何收益。再来看一个例子:

#include <functional>
#include <iostream>
#include <algorithm>

template <typename T>
class rectangle {
public:
    rectangle(void) {}
    
    T area_for_rectangle(T a, T b) { return a * b; }
};

template <typename T>
class circular {
public:
    circular(void) {}
    T area_for_circular(T r) { return 3.1415926 * r * r; }
};

template <typename F, typename G, typename ... Ts>
void report_area(F func, G &obj, Ts ... args) {
    auto area = std::invoke(func, obj, args ...);
    std::cout << "area for " << typeid(G).name() << " is " << area << std::endl;
}

int main(int argc, char **argv)
{
    rectangle<int> rect;
    report_area(&rectangle<int>::area_for_rectangle, rect, 15, 20);
    
    circular<double> circ;
    report_area(&circular<double>::area_for_circular, circ, 1.0);
    
    return 0;

        在这个例子中,std::invoke似乎不可取代。但仔细研究之后,发现其仍然并非不可取代,其取代方案如下:

//...
template <typename F, typename G, typename ... Ts>
void report_area(F func, G &obj, Ts ... args) {
    auto area = (obj.*func)(args ...);
    std::cout << "area for " << typeid(G).name() << " is " << area << std::endl;
}
//...

        仔细分析后会发现,std::invoke并非必须存在,其最大意义在于调用函数指针时,不必再使用复杂的指针语法,std::invoke(func, obj, args ...)总比(obj.*func)(args ...)可读性要好。 

 invoke实现

        要想理解invoke实现,需要理解函数指针,因为invoke的本质就是对函数指针调用的封装。下面是invoke的一个简单实现,虽然简单,但足以说明invoke原理。

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

typedef int (*calc_f)(int, int);
int invoke(calc_f func, int a, int b) { return func(a, b); }

struct calc {
    int add(int a, int b) { return a + b; }
    int sub(int a, int b) { return a - b; }
};

typedef int (calc::*calc_mf)(int, int);
int invoke(calc_mf func, calc &cobj, int a, int b) { return (cobj.*func)(a, b); }

int main(int argc, char **argv)
{
    int _add = invoke(add, 4, 5);
    int _sub = invoke(sub, 7, 3);
    calc cobj;
    _add = invoke(&calc::add, cobj, 40, 5);
    _sub = invoke(&calc::sub, cobj, 70, 3);
    
    return 0;
}

         上面的invoke实现通用性很差。如果要实现一个通用较好的invoke,至少需要解决三点问题:

  • 区分函数指针类型:类成员函数还是全局函数
  • 参数转发处理,参数类型和数量都不确定
  • 确定函数的返回值类型

        关于第一点,上面的例子已经通过函数重载,增加类的对象实例实现;第二点可以通过变参模板实现;第三点对于返回值类型的推导有两种方案:一使用auto,二使用decltype。

使用auto推导返回值类型

//...
//模板1
template <typename Fp, typename ... Args>
auto invoke(Fp func, Args ... args) { return func(args ...); }

//模板2
template <typename Fp, typename Tp, typename ... Args>
auto invoke(Fp func, Tp obj, Args ... args) { return (obj.*func)(args ...); }

//...

    int _add, _sub;
//实例1
    _add = invoke(add, 4, 5);
    _sub = invoke(sub, 7, 3);
//实例2    
    calc cobj;
    _add = invoke(&calc::add, cobj, 40, 5);
    _sub = invoke(&calc::sub, cobj, 70, 3);

//...

         关于上面的源码,调用全局函数和类成员函数的invoke单独编译,是没有问题的。但放到一起,编译无法通过。编译会会使用 invoke(Fp func, Tp obj, Args ... args)推导invoke(add, 4, 5),因此,会出现下面的错误:

error: right hand operand to .* has non-pointer-to-member type 'int (*)(int, int)'
auto invoke(Fp func, Tp obj, Args ... args) { return (obj.*func)(args ...); }

note: in instantiation of function template specialization 'invoke<int (*)(int, int), int, int>' requested here
    _add = invoke(add, 4, 5);

        关于这个无误,解决也比较简单,使用std::enable_if做下判断即可。新的实现如下:

//...
template <typename Fp, typename ... Args>
auto invoke(Fp func, Args ... args) { return func(args ...); }

template <typename Fp, typename Tp, typename ... Args, typename enable = typename std::enable_if<std::is_member_function_pointer<Fp>::value>::type>
auto invoke(Fp func, Tp obj, Args ... args) { return (obj.*func)(args ...); }
//...

使用decltype推导返回值类型 

         返回值类型使用auto进行推导,至少要到C++14才会支持。对于C++11,返回值类型也可以使用decltype关键字进行推断,源码如下:

//...
template <typename Fp, typename ... Args>
decltype(std::declval<Fp>()(std::declval<Args>()...)) invoke(Fp func, Args ... args) { return func(args ...); }

template <typename Fp, typename Tp, typename ... Args>
decltype((std::declval<Tp>().*std::declval<Fp>())(std::declval<Args>()...)) invoke(Fp func, Tp obj, Args ... args) { return (obj.*func)(args ...); }
//...

        使用decltype进行返回值类型推断,需要用到std::declval工具。因为对类成员函数的调用,需要定义类对象,而std::declval使在没有类对象的情况下调用类成员函数称为一种可能。如果不涉及到类的成员函数,完全可以不适用std::cval工具。源码可以简化实现,如下:

//...
template <typename Fp, typename ... Args>
decltype(Fp()(Args()...)) invoke(Fp func, Args ... args) { return func(args ...); }

template <typename Fp, typename Tp, typename ... Args>
decltype((std::declval<Tp>().*std::declval<Fp>())(Args()...)) invoke(Fp func, Tp obj, Args ... args) { return (obj.*func)(args ...); }
//...

        使用decltype与auto比较,尽管代码有失简洁,但使用decltype可以不必在使用std::enable_if进行推导判断。

        这便是invoke的实现,当然标准库的实现会更为复杂,因为其要考虑通用性和灵活性,但其基本原理本文所述一致。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/599194.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

项目计划书(Word原件)

项目开发计划包括项目描述、项目组织、成本预算、人力资源估算、设备资源计划、沟通计划、采购计划、风险计划、项目过程定义及项目的进度安排和里程碑、质量计划、数据管理计划、度量和分析计划、监控计划和培训计划等。 软件资料清单列表部分文档&#xff1a; 工作安排任务书…

如何有效识别限界上下文?

在实施DDD的过程中&#xff0c;识别限界上下文是一大难点&#xff0c;但也并非无章可循。在本文内容中&#xff0c;我们将分别从业务维度、工作维度以及技术维度进行展开&#xff0c;讨论如何有效识别限界上下文的方法和技巧。 从业务维度识别限界上下文 从业务维度识别限界上…

羊大师解析,鲜为人知的羊奶冷知识

羊大师解析&#xff0c;鲜为人知的羊奶冷知识 羊奶的脂肪球更小&#xff1a;相较于牛奶&#xff0c;羊奶中的脂肪球直径更小&#xff0c;这有助于其更快地被人体消化和吸收。 羊奶含有更多的中链脂肪酸&#xff1a;羊奶中含有较多的中链脂肪酸&#xff08;MCT&#xff09;&am…

安装nginx-1.25.5与ngx_http_headers_more_filter_module模块

#下载nginx的代码 curl -O http://nginx.org/download/nginx-1.25.5.tar.gz #下载headers-more-nginx-module代码 git clone https://github.com/openresty/headers-more-nginx-module#解压 tar -xzf nginx-1.25.5.tar.gzcd nginx-1.25.5#--add-dynamic-module 下载下来的目录 …

Al Agent:开启智能化未来的关键角色,让机器更智能的为我们服务

文章目录 &#x1f680;Al Agent是什么&#x1f4d5;Al Agent的工作原理与技术&#x1f4aa;Al Agent应用领域&#x1f680;智能家居应用&#x1f308;医疗健康领域⭐金融服务行业&#x1f302;交通运输管理&#x1f3ac;教育培训应用 &#x1f512;Al Agent优势与挑战✊Al Age…

移动端自适应

基本实现核心思想 基本原则上是&#xff0c;布局更多地使用flex&#xff0c;然后尺寸使用rem&#xff0c;vw&#xff0c;vh为单位如果是根据不同的屏幕需要有不同的布局了&#xff0c;一般通过检测屏幕尺寸换不同的站点或者媒体查询使用css rem 以html字体太小为1rem的大小&…

LM4562NA 直插DIP8双运放 音频hifi运算放大器

LM4562NA是一款高性能音频运算放大器&#xff0c;其应用领域主要集中在音频和声音处理方面&#xff0c;包括但不限于&#xff1a; 1. 专业录音设备&#xff1a;在录音棚、广播电台和电视台等专业环境中&#xff0c;用于信号放大和处理&#xff0c;确保高质量的声音录制和传输…

揭秘数据可视化:五款利器助力决策

在当今这个数据驱动的时代&#xff0c;数据可视化已成为企业决策、数据分析不可或缺的一部分。通过直观、生动的图形、图像&#xff0c;数据可视化能够更快速、更准确地传达信息&#xff0c;帮助企业洞察数据背后的价值。本文将为您介绍几款优秀的数据可视化工具。 一、山海鲸…

docker-compose编排集成工具,consul服务更新与发现

一、引言 我们知道使用一个 Dockerfile 模板文件可以定义一个单独的应用容器&#xff0c;如果需要定义多个容器就需要服务编排。服务编排有很多种技术方案&#xff0c;今天给大家介绍 Docker 官方产品 Docker-Compose Dockerfile 可以定义一个单独的应用容器&#xff1…

图片编辑工具-Gimp

一、前言 GIMP&#xff08;GNU Image Manipulation Program&#xff09;是一款免费开源的图像编辑软件&#xff0c;具有功能强大和跨平台的特性。 GIMP作为一个图像编辑器&#xff0c;它提供了广泛的图像处理功能&#xff0c;包括但不限于照片修饰、图像合成以及创建艺术作品…

uni-app安卓本地打包个推图标配置

如果什么都不配置&#xff0c;默认的就是个推小鲸鱼图标 默认效果 配置成功效果 个推图标配置 新建目录 drawable-hdpi、drawable-ldpi、drawable-mdpi、drawable-xhdpi、drawable-xxhdpi、drawable-xxxhdpi 目录中存放图标 每个目录中存放对应大小的图标&#xff0c;大图…

Day28:ElasticSearch入门、Spring整合ES、开发社区搜索功能

ElasticSearch入门 Elasticsearch简介 一个分布式的、Restful风格的搜索引擎。支持对各种类型的数据的检索&#xff08;非结构化的也可以&#xff09;。搜索速度快&#xff0c;可以提供实时的搜索服务。便于水平扩展&#xff08;集群式部署&#xff09;&#xff0c;每秒可以处…

分享三维地理模型制作实践

前言 地理信息系统&#xff08;GIS&#xff09;是一种用于捕获、存储、检查和显示与地球表面位置相关的数据的计算机系统。GIS可以在一张地图上显示许多不同类型的数据&#xff0c;如街道、建筑物和植被。这使人们能够更容易地看到、分析和理解模式和关系。 实践 从地理空间…

正在载入qrc文件 指定的qrc文件无法找到。您想更新这个文件的位置么?

打开Qt的ui文件&#xff0c;弹出提示框 如果需要用到qrc文件&#xff0c;选择Yes&#xff0c;再选择qrc文件所在的位置&#xff1b;如果不需要qrc文件&#xff0c;可以选择No&#xff0c;然后用普通文本编辑器打开&#xff0c;将“ <resources> <include location&q…

经典面试题---环形链表

1. 环形链表1. - 力扣&#xff08;LeetCode&#xff09; 要解决这道题&#xff0c;我们首先要挖掘出带环的链表与不带环的链表之间的差别。 以此&#xff0c;才能设计出算法来体现这种差别并判断。 二者最突出的不同&#xff0c;就是不带环的链表有尾结点&#xff0c;也就是说…

Golang | Leetcode Golang题解之第71题简化路径

题目&#xff1a; 题解&#xff1a; func simplifyPath(path string) string {stack : []string{}for _, name : range strings.Split(path, "/") {if name ".." {if len(stack) > 0 {stack stack[:len(stack)-1]}} else if name ! "" &am…

Android 系统启动流程源码分析

一、Init进程启动 是一个由内核启动的用户级进程。内核自行启动之后&#xff0c;就通过启动一个用户级程序init的方式&#xff0c;完成引导进程。 启动的代码init.c中的main函数执行过程&#xff1a;system\core\init.c中&#xff1a; 主要下面两个重要的过程&#xff1a; 1…

泰克示波器如何存储CSV文件?

泰克示波器可以用于各种信号的测量和分析。在实际测试中&#xff0c;我们经常需要将示波器采集到的波形数据保存下来&#xff0c;以便后续的处理和分析。泰克示波器提供了多种方法来存储波形数据&#xff0c;其中一种常用的方式是将数据保存为CSV文件。下面将介绍泰克示波器如何…

VINS预积分与误差模型

文章目录 IMU的测量值误差模型IMU预积分真实模型IMU预积分估计模型误差模型普通增量积分中值积分法 参考文献 IMU的测量值误差模型 IMU的测量值误差模型&#xff1a; a ^ t a t R w t g w b a t n a t ω ^ t ω t b ω t n ω t \begin{array}{} {{{\hat a}_t} {a_t…

成功案例(IF=7.3)| 转录组+蛋白质组+代谢组联合分析分析揭示胰腺癌中TAM2相关的糖酵解和丙酮酸代谢重构

研究背景 肿瘤的进展和发展需要癌细胞的代谢重编程&#xff0c;癌细胞能量代谢模式的改变可以满足快速增殖和适应肿瘤微环境的需要。肿瘤微环境&#xff08;TME&#xff09;中的代谢状态受到多种因素的影响&#xff0c;包括血管生成、与其他细胞的相互作用和系统代谢。代谢异质…
最新文章