(CPP20) 简单实现span

文章目录

  • 视图的概念
  • 常见连续序列测试
  • 简单实现
    • Code
    • 直接引用对象
    • 传入首位置
  • END

视图的概念

在不传递引用的情况下,传递对象在很多时候会巨大的性能损耗。

而入错传递的是一个视图,这个视图能够指向原对象,那么这个直接传递的开销也是我们可以接受的。

在C++20中,std::span是一种能够指代连续序列的数据结构。

std::span - cppreference.com

具体效果如下:

#include <iostream>
#include <span>
#include <vector>

struct Node {
    int x = 0;
    Node(int x) : x(x) {
        printf("[%s]=>[%p]=>[%d]\n", __func__, this, x);
    }

    Node(const Node& lhs) {
        x = lhs.x;
        printf("[%s]=>[%p=>%p]=>[%d]\n", __func__, &lhs, this, x);
    }

    operator int() {
        return x;
    }
};

void show_by_vec(std::vector<Node> arr) {
    std::cout << __func__ << std::endl;
    for (auto&& x : arr) {
        std::cout << x << ' ';
    }
    std::cout << std::endl;
}

void show_by_span(std::span<Node> arr) {
    std::cout << __func__ << std::endl;
    for (auto&& x : arr) {
        std::cout << x << ' ';
    }
    std::cout << std::endl;
}

int main() {
    std::vector<Node> arr = {1, 2, 3};
    std::cout << "++++++++++++++++++++++++++++" << std::endl;
    show_by_vec(arr);
    show_by_span(arr);
}

输出如下:

从输出可以看出,span相比较于直接传递对象不会有过多的开销

[Node]=>[000001368f2513e0]=>[1]
[Node]=>[000001368f2513e4]=>[2]
[Node]=>[000001368f2513e8]=>[3]
++++++++++++++++++++++++++++
[Node]=>[000001368f2513e0=>000001368f251400]=>[1]
[Node]=>[000001368f2513e4=>000001368f251404]=>[2]
[Node]=>[000001368f2513e8=>000001368f251408]=>[3]
show_by_vec
1 2 3
show_by_span
1 2 3

常见连续序列测试

常见的连续序列大概有以下几种

  • C式数组
  • std::vector
  • std::array

注意:

  • std::list, std::deque这些都不是连续序列
  • 特别的,变长数组虽然也是连续的,但是不能用与std::span
#include <array>
#include <iostream>
#include <list>
#include <span>
#include <vector>

namespace name = std;
void show(name::span<int> arr) {
    for (auto&& elem : arr) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;
}

int main() {
    // 传统的C式数组
    int c_arr[] = {1, 2, 3, 4};
    show(c_arr);                      // 直接传数组
    show({c_arr, std::size(c_arr)});  // 传递指针+长度

    // std::array<T, N>
    std::array arr = {11, 22, 33, 44};
    show(arr);  // 直接传std::array

    // std::vector
    std::vector vec{111, 222, 333, 444};
    show(vec);                        // 直接传vector
    show({vec.begin(), vec.end()});   // 首尾迭代器
    show({vec.begin(), vec.size()});  // 首迭代器+长度

    // 链表不是连续序列
    // std::list lst = {1111, 2222, 3333, 4444};
    // show(list);
    
    // std::deque 也不是
}

简单实现

注意本文只是一个简单实现版本

没有考虑各种特殊情况,也没有用 SFINAE(Substitution Failure Is Not An Error)来进行约束。

一些简单的约束方法可以参考 > (C++) 一个例子,了解 SFINAE 从 cpp11 到 cpp20 的核心技巧 - 知乎 (zhihu.com)

Code

直接上代码:

#include <array>
#include <memory>  // cpp20 std::to_address
#include <vector>

namespace my {

// 只考虑一个模板参数的情况
template <typename Type>
class span final {
private:
    Type*  ptr  = nullptr;
    size_t size = 0;

public:
    constexpr span() noexcept                       = default;
    constexpr span(const span&) noexcept            = default;
    constexpr span& operator=(const span&) noexcept = default;
    constexpr  ~span() noexcept                     = default;

public:  // 针对具体类型
    // 数组是无法传递对象,只能传递引用
    template <size_t N>
    constexpr span(Type (&arr)[N]) noexcept {
        this->ptr  = arr;
        this->size = N;
    }

    // 针对std::array
    template <size_t N>
    constexpr span(std::array<Type, N>& arr) noexcept {
        this->ptr  = arr.data();
        this->size = N;
    }

    // 针对std::vector
    constexpr span(std::vector<Type>& arr) noexcept {
        this->ptr  = arr.data();
        this->size = arr.size();
    }

public:  // 针对迭代式 使用cpp20的`std::to_address`可以同时应对迭代器和指针
    // 首位置和长度
    template <typename Iter>
    constexpr span(Iter iter, size_t length) noexcept {
        this->ptr  = std::to_address(iter);
        this->size = length;
    }

    // 首位置和尾位置
    template <typename Iter>
    constexpr span(Iter first, Iter end) noexcept {
        this->ptr  = std::to_address(first);
        this->size = end - first;
    }

public:
    constexpr Type* begin() const noexcept {
        return this->ptr;
    }
    constexpr Type* end() const noexcept {
        return this->ptr + this->size;
    }
};
}  // namespace my

测试代码与上文一致:

提示:将该模块的两份代码直接顺序复制到一个文档中即可

#include <iostream>
namespace name = my;
void show(name::span<int> arr) {
    for (auto&& elem : arr) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;
}

int main() {
    // 传统的C式数组
    int c_arr[] = {1, 2, 3, 4};
    show(c_arr);                      // 直接传数组
    show({c_arr, std::size(c_arr)});  // 传递指针+长度

    // std::array<T, N>
    std::array arr = {11, 22, 33, 44};
    show(arr);  // 直接传std::array

    // std::vector
    std::vector vec{111, 222, 333, 444};
    show(vec);                        // 直接传vector
    show({vec.begin(), vec.end()});   // 首尾迭代器
    show({vec.begin(), vec.size()});  // 首迭代器+长度
}

这里的主要难点就是对构造函数的实现,如何获得首地址和长度。

大致可以分为两类

  1. 直接引用对象
  2. 传入首位置

直接引用对象

关于std::vector和std::array相对比较方便,也是大家平时直接应用的方式。

而关于数组,很多人并不熟悉怎么操作。

注意Type (&arr)[N]的写法这里必须要套小括号,因为[]的优先级比&高,不然无法获得数组的引用。

std::array和普通数组一样也是使用template <size_t N>。可见具有动态长度的std::vector在这里是最方便的。

public:  // 针对具体类型
    // 数组是无法传递对象,只能传递引用
    template <size_t N>
    constexpr span(Type (&arr)[N]) noexcept {
        this->ptr  = arr;
        this->size = N;
    }

    // 针对std::array
    template <size_t N>
    constexpr span(std::array<Type, N>& arr) noexcept {
        this->ptr  = arr.data();
        this->size = N;
    }

    // 针对std::vector
    constexpr span(std::vector<Type>& arr) noexcept {
        this->ptr  = arr.data();
        this->size = arr.size();
    }

传入首位置

首地址+长度是C语言中传数组的最常见形式。

到了C++中泛化出了迭代器的标准。为了同时适配普通指针和迭代器,在C++20中推出了std::to_address()的方法。可以多态的转化到指针中。这样就不用针对指针和迭代器写两个版本了。

关于这里的第二个传入首位位置的版本,需要允许两者直接的相互减。

请注意各种迭代器的区别:迭代器库 - cppreference.com

public:  // 针对迭代式 使用cpp20的`std::to_address`可以同时应对迭代器和指针
    // 首位置和长度
    template <typename Iter>
    constexpr span(Iter iter, size_t length) noexcept {
        this->ptr  = std::to_address(iter);
        this->size = length;
    }

    // 首位置和尾位置
    template <typename Iter>
    constexpr span(Iter first, Iter end) noexcept {
        this->ptr  = std::to_address(first);
        this->size = end - first;
    }



END

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

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

相关文章

Redis——某马点评day01——短信登录

项目介绍 导入黑马点评项目 项目架构 基于Session实现登录 基本流程 实现发送短信验证码功能 controller层中 /*** 发送手机验证码*/PostMapping("code")public Result sendCode(RequestParam("phone") String phone, HttpSession session) {// 发送短信…

支持Upsert、Kafka Connector、集成Airbyte,Milvus助力高效数据流处理

Milvus 已支持 Upsert、 Kafka Connector、Airbyte&#xff01; 在上周的文章中《登陆 Azure、发布新版本……Zilliz 昨夜今晨发生了什么&#xff1f;》&#xff0c;我们已经透露过 Milvus&#xff08;Zilliz Cloud&#xff09;为提高数据流处理效率&#xff0c; 先后支持了 Up…

JOSEF约瑟 DY-34 型电压继电器,15-30V 柜内安装,板前接线

DY-30系列电压继电器 DY-32电压继电器&#xff1b; DY-36电压继电器&#xff1b; DY-33电压继电器&#xff1b; DY-37电压继电器&#xff1b; DY-34电压继电器&#xff1b; DY-38电压继电器&#xff1b; DY-31电压继电器&#xff1b; DY-35电压继电器&#xff1b; DY-32/60C电压…

HarmonyOS——解决本地模拟器无法选择设备的问题

在使用deveco studio进行鸿蒙开发的时候&#xff0c;可能会遇到本地模拟器已经启动了&#xff0c;但是仍然无法选择本地模拟器中的设备&#xff0c;尤其在MAC环境中尤为常见。 解决办法&#xff1a; 先打开IDE启动本地模拟器&#xff0c;等模拟器启动后&#xff0c;退出IDE重新…

蓝桥杯每日一题2023.12.1

题目描述 蓝桥杯大赛历届真题 - C 语言 B 组 - 蓝桥云课 (lanqiao.cn) 题目分析 对于此题目而言思路较为重要&#xff0c;实际可以转化为求两个数字对应的操作&#xff0c;输出最前面的数字即可 #include<bits/stdc.h> using namespace std; int main() {for(int i 1…

ARM64版本的chrome浏览器安装

这一快比较玄学&#xff0c;花个半个小时左右才能安装好&#xff0c;也不知道是个什么情况。 sudo snap install chromium只需要以上这个命令&#xff0c;当然&#xff0c;也可以自己去找安装包进行安装&#xff0c;但是测试后发现并没有那么好装&#xff0c;主要是两个部分 一…

第九节HarmonyOS 常用基础组件-Text

一、组件介绍 组件&#xff08;Component&#xff09;是界面搭建与显示的最小单位&#xff0c;HarmonyOS ArkUI声名式为开发者提供了丰富多样的UI组件&#xff0c;我们可以使用这些组件轻松的编写出更加丰富、漂亮的界面。 组件根据功能可以分为以下五大类&#xff1a;基础组件…

Unity中Shader指令优化

文章目录 前言一、解析一下不同运算所需的指令数1、常数基本运算2、变量基本运算3、条件语句、循环 和 函数 前言 上一篇文章中&#xff0c;我们解析了Shader解析后的代码。我们在这篇文章中来看怎么实现Shader指令优化 Unity中Shader指令优化&#xff08;编译后指令解析&…

C语言之联合和枚举

C语言之联合和枚举 文章目录 C语言之联合和枚举1. 联合体1.1 联合体的声明1.2 联合体的特点1.3 结构体和联合体对比1.4 联合体大小的计算1.5 联合体小练习 2. 枚举2.1 枚举类型的声明2.2 枚举类型的优点2.3 枚举类型的使用 1. 联合体 1.1 联合体的声明 像结构体⼀样&#xff…

AI PC行业深度报告:格局演变、发展趋势、产业链及相关公司深度梳理

今天分享的是AI PC系列深度研究报告&#xff1a;《AI PC行业深度报告&#xff1a;格局演变、发展趋势、产业链及相关公司深度梳理》。 &#xff08;报告出品方&#xff1a;慧博智能投研&#xff09; 报告共计&#xff1a;21页 一、AI PC的产生 1、端侧 AI 是 AI 发展下一阶段…

【ASP.NET CORE】数据迁移 codefirst

已经写好实体类&#xff0c;使用add-migration生成数据迁移语句&#xff0c;注意如果项目中有多个dbcontext需要使用 -context 名称&#xff0c;指定下需要使用的dbcontext add-Migration Address -context mvcsqlcontext运行后会生成两个文件 2. 使用Update-Database语句更…

Postman如何导入和导出接口文件

本文介绍2种导出和导入的操作方法&#xff1a;一种是分享链接&#xff0c;导入链接的方式&#xff08;需要登录&#xff09;&#xff1b;另一种是导出json文件&#xff0c;再次导入。下面将详细介绍。 由于第一种分享链接&#xff0c;导入链接的方式需要登录&#xff0c;所以推…

Nacos 架构原理

基本架构及概念​ 服务 (Service)​ 服务是指一个或一组软件功能&#xff08;例如特定信息的检索或一组操作的执行&#xff09;&#xff0c;其目的是不同的客户端可以为不同的目的重用&#xff08;例如通过跨进程的网络调用&#xff09;。Nacos 支持主流的服务生态&#xff0c…

【每日一题】找出叠涂元素

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;哈希表 写在最后 Tag 【哈希表】【数组】【2023-12-01】 题目来源 2661. 找出叠涂元素 题目解读 从左往右遍历 arr 给矩阵 mat 上色&#xff0c;在上色的过程中矩阵的某一行或者某一列的全部被上色了&#xff0c;返回…

Git的介绍和下载安装

Git的介绍和下载安装 概述 Git是一个分布式版本控制工具, 通常用来管理项目中的源代码文件(Java类、xml文件、html页面等)进行管理,在软件开发过程中被广泛使用 Git可以记录文件修改的历史记录并形成备份从而实现代码回溯, 版本切换, 多人协作, 远程备份的功能Git具有廉价的…

leecode 【二】

相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返…

(数据结构)顺序表的插入删除

#include<stdio.h> #include<stdlib.h> #define MAX 10 typedef struct {int data[MAX];int lenth; }List; //初始化 void CreateList(List* L) {L->lenth 0;for (int i 0; i < MAX; i){L->data[i] 0;} } //插入 int ListInsert(List* L,int i,int e) …

STM32学习笔记--闪存Flash

STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过闪存存储器接口&#xff08;外设&#xff09;可以对程序存储器和选项字节进行擦除和编程。 读写FLASH的用途&#xff1a;利用程序存储器的剩余空间来保存掉电不丢失的用户数据 &#xff0c;通过…

【数值计算方法(黄明游)】矩阵特征值与特征向量的计算(二):Jacobi 过关法(Jacobi 旋转法的改进)【理论到程序】

文章目录 一、Jacobi 旋转法1. 基本思想2. 注意事项 二、Jacobi 过关法1. 基本思想2. 注意事项 三、Python实现迭代过程&#xff08;调试&#xff09; 矩阵的特征值&#xff08;eigenvalue&#xff09;和特征向量&#xff08;eigenvector&#xff09;在很多应用中都具有重要的数…