[C++] std::ranges中的特征和自定义std::ranges::view变换

news2024/11/17 7:47:21

文章目录

    • 1. std::ranges中的特征
      • 1.1. std::ranges::range
        • 例子
        • 细化
      • 1.2. std::ranges::sized_range
      • 1.3. std::ranges::borrowed_range
      • 1.4. std::ranges::view
    • 2. std::ranges::subrange 迭代器-哨位对
      • 2.1. 构造
      • 2.2. 结构化解绑
      • 2.3. 操作
    • 3. std::views中的std::ranges::view变换
      • 3.1. std::ranges::view工厂构造
      • 3.2. std::views中的变换构造对象
      • 3.3. operator|()的流式变换
      • 3.4. 与其他语言的迭代器的变换操作比较
      • 3.5. 自定义std::ranges::view变换
        • 3.5.1. 常用的变换实现参考
    • 5. 迭代器特征
      • 4.1. std::input_or_output_iterator
      • 4.2. std::forward_iterator
      • 4.3. std::semiregular

为了方便诠释, 下面的定义均使用通俗易懂的叙述, 可能与实际定义有所出入, 一切以C++中定义的concept结果为准

自C++20起, 基于编译时多态的面向特征(trait)开始流行, 取代了面向对象. 如std::format也全面转向面向特征, 并获得了领先的运行效率和高度自由的扩展能力. 复杂的多继承转变为正交的特征组合, 是面向特征的一大特色

有命名空间的化简using std::views = std::ranges::views

1. std::ranges中的特征

下图是特征总览
在这里插入图片描述

1.1. std::ranges::range

struct Type {
	iterator begin();
	sentinel end();
}

要求

  • std::input_or_output_iterator iterator
  • std::semiregular sentinel
  • iteratorsentinel可做相等性比较

例子

下面是一个符合要求的std::ranges::range

struct Type {
	int* begin();
	int* end();
};
static_assert(std::ranges::range<MyType>);

C++20前一般要求begin和end都返回一个迭代器, 在范围库中放宽了限制, 对于end只要能返回一个可比较的对象即可, 不要求对象可做迭代器都可以做的++运算. 因此不再称end返回的对象为迭代器(iterator), 而称之为哨位(sentinel)

可以用std::ranges::iterator_t<T>std::ranges::sentinel_t<T>从类型T中获取迭代器类型和哨位类型.
如要迭代器类型成立, 只需实现begin
如要哨位类型成立, 除了需要实现end, 还要实现begin

细化

设有std::ranges::range T, begin返回类型为iterator = std::ranges::iterator_t<T>, end返回类型为sentinel = std::ranges::sentinel_t<T>

  • 如果std::input_iterator iterator, 则有std::ranges::input_range T
  • 如果std::output_iterator iterator, 则有std::ranges::output_range T
  • 如果std::forward_iterator iterator, 则有std::ranges::forward_range T
  • 如果std::bidirectional_iterator iterator, 则有std::ranges::bidirectional_range T
  • 如果std::random_access_iterator iterator, 则有std::ranges::random_access_range T
  • 如果std::contiguous_iterator iterator, 则有std::ranges::contiguous_range T
  • 如果iterator == sentinel, 则有std::ranges::common_range T
  • 如果std::ranges::view Tstd::ranges::borrowed_range T, 则有std::ranges::viewable_range T

1.2. std::ranges::sized_range

struct Type {
	iterator begin();
	sentinel begin();
	size_t size(); // 可选
}

要求

  • std::ranges::range Type
  • 提供size函数
  • 能常数时间获取范围的长度

如果未提供size函数, 那么还要求

  • std::forward_iterator iterator
  • iteratorsentinel(及iterator)可作差

如果size或者iterator的作差不能用常数时间实现, 那么可以用特化

  • std::ranges::disable_sized_range<T> = true
  • std::disable_sized_sentinel_for<iterator, iterator> = true

强制关闭std::ranges::sized_rangestd::ranges::sized_sentinel_for的特征

1.3. std::ranges::borrowed_range

如果类型std::ranges::range T的值T tt.begin()t.end()获得的迭代器的生命周期与t无关, 那么可以认为是std::ranges::borrowed_range

由于语言层面无法自动识别生命周期的关系, 因此要特征能被识别, 还要手动特化std::ranges::enable_borrowed_range<T>true

但特别地, 类型T&自然满足std::ranges::borrowed_range

1.4. std::ranges::view

如果std::ranges::range的复制只需要常数的时间, 那么可以认为是std::ranges::view
由于语言层面无法自动识别复制所需的时间复杂度, 因此要特征能被识别, 还要手动开启特征

对于std::ranges::range T, 满足以下条件之一, 即认为实现std::ranges::view特征

  • 实现std::movable且特化std::ranges::enable_view<T>true
  • 或继承std::ranges::view_base
  • 或继承std::ranges::view_interface<T>

另外如果std::ranges::range T继承std::ranges::view_interface<T>, 那么在T满足一些条件时, 还能自动获得以下函数

  • empty(), 要求std::ranges::forward_range T
  • operator bool(), 要求std::ranges::forward_range T
  • data(), 要求std::contiguous_range T
  • size(), 要求std::ranges::forward_range T, 且iteratorsentinel(及iterator)可作差
  • front(), 要求std::ranges::forward_range T
  • back(), 要求std::ranges::bidirectional_range Tstd::ranges::common_range T
  • operator[](), 要求std::ranges::random_access_range T

2. std::ranges::subrange 迭代器-哨位对

2.1. 构造

将迭代器(Iterator i)和哨位(Sentinel s)结合为std::ranges::view std::ranges::subrange<Iterator, Sentinel>类型的对象, 满足std::ranges::viewable_ranges, 并且当

  • is对象可作差
  • 或手动指定类型为std::ranges::subrange<I, S, std::ranges::subrange_kind::sized>
  • 或显式传递大小参数时

类型还实现std::ranges::sized_range

2.2. 结构化解绑

可以使用结构化解绑获取迭代器和哨位

auto [i, s] = subrange;

也可以用std::get<0>std::get<1>分别获取迭代器和哨位

2.3. 操作

函数名操作简介返回值要求
next增加迭代器新的std::ranges::subrangestd::ranges::forward_iterator Iterator
prev减少迭代器新的std::ranges::subrangestd::ranges::bidirectional_iterator Iterator
advance自增/自减迭代器自身

以上操作只在迭代器增加/自增时有边界检查

3. std::views中的std::ranges::view变换

3.1. std::ranges::view工厂构造

  • 对象std::views::empty, void -> view, 使用std::views::empty<T>即可直接获得对象
  • 对象std::views::single, any -> view, 单对象的std::ranges::view
  • 对象std::views::iota, iterator | (iterator, sentinel) -> view, 一般哨位边界的有限或无限递增序列
  • 对象std::views::counted, (iterator, count) -> view, 计数哨位边界的有限递增序列
  • 对象std::views::istream<T>, istream<T> -> view, 输入流转std::ranges::view
  • 类型std::ranges::subrange, (iterator, sentinel, [size]) | (borrowed_range, [size]) -> subvrange, 迭代器-哨位对
  • 类型std::ranges::ref_view, range -> viewable_range 借用
  • 类型std::ranges::owning_view, range -> viewable_range 占用
  • 对象std::views::repeat(C++23), 重放
  • 对象std::views::cartesian_product(C++23), 笛卡尔积
  • 等等

3.2. std::views中的变换构造对象

  • std::views::all, view -> ref_view | owning_view 借用或占用
  • std::views::filter(invokable), input_range & view -> input_range & view 过滤
  • std::views::transform(invokable), input_range & view -> input_range & view 映射
  • std::views::take(int), view -> subrange 取前一部分
  • std::views::join, input_range<input_range> & view -> input_range & view 展平
  • std::views::split(forward_range & view), forward_range & view -> subrange<forward_range & view> 序列中的指定子序列为分割点划分序列
  • std::views::common, view -> common_range 同化iterator_tsentinel_t的类型, 以兼容旧的库函数

更多可见3.4. 与其他语言的迭代器的变换操作比较

3.3. operator|()的流式变换

std::views中的对象多为二段可调, 部分不需要参数的为一段可调

  • 二段可调
    std::views::take, 一段调用auto c = std::views::take(10)获得变换c, 之后二段调用c(range)才真正施行变换
  • 一段可调
    std::views::join, 其自身就是变换, 一段调用std::views::join(range)即可施行变换

变换都实现有operator|运算, 可对运算左侧的对象实施变换, 如

range | std::views::all | std::views::common;
/* 等价于 */ std::views::common(std::views::all(range));

二段可调的对象一般不是变换, 需要赋予参数进行一段调用后才能得到变换

range | std::views::take(5) | std::views::filter([](auto const& it) { return true; });
/* 等价于 */ std::views::filter([](auto const& it) { return true; })(std::views::take(5)(range));

3.4. 与其他语言的迭代器的变换操作比较

其他语言的迭代器一般自带哨位, 对应到C++来实际上是std::ranges::range的概念
以kotlin为例 Flow, Channel, Sequence, Iterable的接口对比

-kotlin Iterator/SequenceC++20
编号withIndex/
遍历onEach
forEach
/
std::ranges::for_each
取值first
last
single
front
back
std::views::single(front())
查值contains
elementAt
find
findLast
indexOf
contains (部分)
operator[]
std::ranges::find std::ranges::find_if
std::ranges::find_end
/
归约fold
reduce
scan
toXxx
std::accumulate
/
/
std::views::to (C++23)
统计count
all
any
none
average
maxOf
minOf
sum
std::ranges::count std::ranges::count_if
std::ranges::all_of
std::ranges::any_of
std::ranges::none_of
/
std::max_element
std::min_element
/
Map化associate
groupBy
/
/
拣选Map.keys
Map.values
/
std::views::keys
std::views::values
std::views::element
局部take
takeWhile
drop
dropWhile
windowed
/
std::views::take
std::views::take_while
std::views::drop
std::views::drop_while
/
std::views::stride (C++23)
过滤filterstd::views::filter
映射map
/
/
std::views::transform
std::views::zip_transform (C++23)
std::views::adjacent_transform (C++23)
组合zip
zipWithNext
/
/
std::views::zip (C++23)
/
std::views::slide (C++23)
std::views::adjacent (C++23)
解配对unzip/
合并plus/
二分partitionstd::ranges::partition
平坦化flatMap
flatten
joinTo
/
std::views::join
std::views::join_with
拆分String.splitstd::views::split
std::views::lazy_split
内组合chunked
/
std::views::chunk (C++23)
std::views::chunk_by (C++23)
值去抖distinct/
集合运算minus
intersect
subtract
union
/
/
/
/
重排shuffled
sorted
reverse
std::ranges::shuffle
/
std::views::reverse

3.5. 自定义std::ranges::view变换

可以走以下步骤

  • 写一个作为包装适配器的视图类, 至少实现std::ranges::range特征, 最好实现std::ranges::view特征
  • 写一个内部的迭代器类, 至少实现std::input_iterator特征
  • 如果视图类做变换不需要参数
    写一个可调用对象(函数也行), 接收参数转发给视图类
  • 如果视图类做变换需要参数
    写一个二段可调的可调用对象, 首次调用时传入参数做绑定, 二次调用时接收被变换的std::ranges::range, 转发给视图类

通常情况下, 都可以借助std::views::transform等基本工具来创建自定义变换, 如

inline constexpr auto plus(int n) {
    return std::views::transform([=](auto&& it) {
        return std::forward<decltype(it)>(it) + n;
    });
};

然后就可有

range | plus(123);

3.5.1. 常用的变换实现参考

  • with_index: 附加计数器
inline constexpr auto with_index(size_t start = 0) {
    return std::views::transform([index = start](auto&& it) mutable {
        return std::make_tuple(index++, std::forward<decltype(it)>(it));
    });
};

用法

for (auto const& [index, value] : range | with_index()) {}
  • subtract: 差集
inline constexpr auto subtract(auto&& container) {
    return std::views::filter([cont = std::forward<decltype(container)>(container)](auto&& it) {
        return std::ranges::find(cont, it) == std::ranges::end(cont);
    });
}

用法

range | subtract(std::array{ v1, v2, v3 });
  • to: 输出到容器 (标准库版本在C++23实现) (只处理右值, 未正确处理左值引用)
template<typename Cont>
struct ToFn {
    auto operator()(std::ranges::range auto&& r) {
        std::ranges::copy(std::forward<decltype(r)>(r), std::back_inserter(cont));
        return std::move(cont);
    }
    Cont cont;
};

template<typename Cont>
auto operator|(std::ranges::range auto&& r, ToFn<Cont>&& toFn) {
    return std::move(toFn)(std::forward<decltype(r)>(r));
}

inline constexpr auto to(auto&& container) {
    return ToFn<decltype(container)>{ std::forward<decltype(container)>(container) };
}

用法

auto vec = std::array{1, 2, 3} | to(std::vector<int>{});
  • on_each_indexed: 附加计数的遍历
inline constexpr auto on_each_indexed(auto&& func, size_t start = 0) {
    return std::views::filter([index = start, func = std::forward<decltype(func)>(func)](auto& value) mutable {
        func(index++, value);
        return true;
    });
}

注: 变换一般为惰性的, 需要用for遍历激活流, 否则流的计算不会触发
用法

for (auto const& item : arr | on_each_indexed([](size_t index, auto& value) {
    std::cout << index << ":" << value << ",";
})) {}

5. 迭代器特征

下面是特征关系总览
在这里插入图片描述

4.1. std::input_or_output_iterator

下面是一个符合要求的std::input_or_output_iterator

struct Iter {
    using difference_type = ptrdiff_t;
    MyIter& operator++() { return *this; }
    MyIter operator++(int) { return *this; }
    int operator*() const { return 0; }
};

4.2. std::forward_iterator

下面是一个符合要求的std::forward_iterator

struct Iter {
    using difference_type = ptrdiff_t;
    using value_type = int;
    MyIter& operator++() { return *this; }
    MyIter operator++(int) { return *this; }
    int operator*() const { return 0; }
    bool operator==(MyIter const& other) const { return true; }
};

4.3. std::semiregular

下面是一个符合要求的std::semiregular

struct Type {};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何安装 Elasticsearch

实战场景 如何安装 Elasticsearch 知识点 •CentOS •Java •Elasticsearch 安装 •Kibana 安装 菜鸟实战 Elasticsearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c;基于 RESTful web 接口。Elasticsearch 是用 Java 语言开发的…

编译器设计(十一)——标量优化

文章目录一、简介二、消除无用和不可达代码2.1 消除无用代码2.2 消除无用控制流2.3 消除不可达代码三、代码移动3.1 缓式代码移动3.2 代码提升四、特化4.1 尾调用优化4.2 叶调用优化4.3 参数提升五、冗余消除5.1 值相同与名字相同5.2 基于支配者的值编号算法六、为其他变换制造…

ubuntu 创建桌面快捷启动

前言&#xff1a; ubuntu系统通常不会在桌面上生成启动图标&#xff0c;一般需要自己建一个下面提供常用的两个模板&#xff0c; 1.启动其他程序 2.启动文件快捷方式 一、创建其他程序的启动快捷图标 用pycharm2022为例子 touch pycharm2022.desktop gedit pycharm2022.d…

为什么 JVM 叫做基于栈的 RISC 虚拟机?

为什么 JVM 叫做基于栈的 RISC 虚拟机&#xff1f; 其实这个问题比较简单&#xff0c;今天这篇文章的主要目的是想让大家看一下分析这个问题的逻辑&#xff0c;并且如何更好地从一手资料里寻找这些问题的答案。 上图是《深入理解 Java 虚拟机》一书中的截图。其实&#xff0c;…

吊打面试官,HashMap 这一篇就够了

一、HashMap的简单使用 HashMap集合的创建&#xff1a; Map<String,String> map new HashMap<String,String>(); 使用put存储数据&#xff1a; map.put("张三","喜欢打游戏"); map.put("李四","喜欢睡觉"); map.put(…

电网运行信息检索系统的设计与实现

摘要 电网运行方式管理直接决定了电网企业的经济效益和安全效益,随着我国经济和社会的高速发展&#xff0c;我国电网的覆盖面积、网络节点和电压等级也高速增长。但是,我国当前电网运行方式管理工作水平还相对落后&#xff0c;制约了电网的安全经济效益。本文较为详细的分析了电…

第三章《数组与循环》第2节:多维数组

3.1小节介绍的数组都是把数组元素从逻辑上排成一行,因此它们被称为“一维数组”。如果一个班级内有15个学生,这些学生按照身高又分成3排就坐,其排列形式如图3-2所示: 3-2学生身高排列图 如果程序员希望按照每个学生在班级内的位置来存储他们的身高数据该怎么办呢?一些读者…

基于Java+Spring+Vue+elementUI大学生求职招聘系统详细设计实现

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取联系&#x1f345;精彩专栏推荐订阅&#x1f447;&#x1f…

【项目设计】自主HTTP服务器

文章目录项目介绍网络协议栈介绍协议分层数据的封装与分用HTTP相关知识介绍HTTP的特点URL格式URI、URL、URNHTTP的协议格式HTTP的请求方法HTTP的状态码HTTP常见的HeaderCGI机制介绍CGI机制的概念CGI机制的实现步骤CGI机制的意义日志编写套接字相关代码编写HTTP服务器主体逻辑HT…

Windows AppData介绍

appdata是什么文件夹?&#xff08;应用程序数据&#xff09; 此文件夹是有关帐户信息、系统桌面、安装文件记录、快速启动文件夹等内容的。appdata下有三个子文件夹local&#xff0c;locallow&#xff0c;loaming。当你解压缩包时如果不指定路径&#xff0c;系统就把压缩包解到…

OpenCV图像处理——停车场车位识别

总目录 图像处理总目录←点击这里 十九、停车场车位识别 19.1、项目说明 唐宇迪老师的——OPENCV项目实战 学习 本项目的目的是设计一个停车场车位识别的系统&#xff0c;能够判断出当前停车场中哪些车位是空的。 任务共包含部分&#xff1a; 对图像预处理 从停车场的监控…

Vue基础之组件通信provide、inject

最近发现竟然还有小伙伴还不清楚provide、inject的用法&#xff0c;是时候普及一下provide、inject了&#xff1b; 常用的组件通信基本是父子组件通过props和emit来进行&#xff0c;一旦层级多了起来&#xff0c;props和emit就不好使了。每级都写props的话&#xff0c;会变得非…

PMP每日一练 | 考试不迷路-11.24(包含敏捷+多选)

11.27PMP考试倒计时 3天 每日5道PMP习题助大家上岸PMP&#xff01; ​题目1-2&#xff1a; ​1.在项目的规划阶段完成以后&#xff0c;但在正式执行之前&#xff0c;项目经理需要就项目目标进行沟通并获得承诺。项目经理下一步应该做什么? ( ) A.与所有相关方召开开工会议…

麻了,别再为难软件测试员了

前言 有不少技术友在测试群里讨论&#xff0c;近期的面试越来越难了&#xff0c;要背的八股文越来越多了,考察得越来越细&#xff0c;越来越底层&#xff0c;明摆着就是想让我们徒手造航母嘛&#xff01;实在是太为难我们这些测试工程师了。 这不&#xff0c;为了帮大家节约时…

深度操作系统20.5发布 deepin 20.5更新内容汇总

深度操作系统&#xff08;deepin&#xff09;是一款致力于为全球用户提供美观易用、安全稳定服务的Linux发行版&#xff0c;同时也一直是排名最高的来自中国团队研发的Linux发行版。深度操作系统20.5升级Stable内核至5.15.24&#xff0c;修复底层漏洞&#xff0c;进一步提升系统…

linux 清理垃圾文件

linux的文件系统比windows的要优秀&#xff0c;不会产生碎片&#xff0c;对于长时间运行的服务器来说尤为重要&#xff0c;而且linux系统本身也不会像windows一样产生大量的垃圾文件。不知道这个说法有没有可信度!至少我们可以确定的是linux系统的文件系统是比较优秀的! linux…

如何建立一个自己的网站?不懂代码搭建自己网站详细教程

搭建自己网站的准备&#xff1a; 1、首先需要注册购买一个域名&#xff0c;比如baidu.com&#xff0c;域名注册可以在阿里云或者其它域名注册平台注册。最常见的.com域名一般也就几十元一年&#xff1b; 域名 2、购买一个服务器&#xff0c;服务器也可以在阿里云或者景安等平…

数据结构之希尔排序

希尔排序 在插入排序的基础上&#xff0c;进行完善的算法 举个例子 如图我们把相距举例为4的两个元素组成一个子表 &#xff08;1和5&#xff0c;2和6&#xff0c;3和7&#xff0c;4和8&#xff09; 对各个子表进行直接插入排序 比如对子表2进行直接插入排序 13插入到38前&…

智慧小镇解决方案-最新全套文件

智慧小镇解决方案-最新全套文件一、建设背景二、建设思路三、建设方案四、获取 - 智慧小镇全套最新解决方案合集一、建设背景 智慧小镇&#xff0c;是指在城镇发展过程中&#xff0c;在城镇基础设施、资源环境、社会民生、经济产业、市政治理等领域中&#xff0c;充分利用物联…

2022亚太杯C题思路代码分析

C题就是数学比较开放的题目了&#xff0c;属于一个数据分析类题目&#xff0c;跟前两年的华为杯差不多&#xff0c;考察的也是全球变暖问题。更多内容文末名片查看 问题1.你同意有关全球气温的说法吗&#xff1f;使用2022_APMCM_C_Data。附件中的csv和其他您的团队收集的数据集…