热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

文章目录1.std::ranges中的特征1.1.std::ranges::range例子细化1.2.std::ranges::sized_range1.3.std::ranges




文章目录


    • 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&#43;&#43;20前一般要求begin和end都返回一个迭代器, 在范围库中放宽了限制, 对于end只要能返回一个可比较的对象即可, 不要求对象可做迭代器都可以做的&#43;&#43;运算. 因此不再称end返回的对象为迭代器(iterator), 而称之为哨位(sentinel)

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


细化

设有std::ranges::range T, begin返回类型为iterator &#61; std::ranges::iterator_t, end返回类型为sentinel &#61; std::ranges::sentinel_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 &#61;&#61; 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 &#61; true
  • std::disable_sized_sentinel_for &#61; 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_rangetrue

但特别地, 类型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_viewtrue
  • 或继承std::ranges::view_base
  • 或继承std::ranges::view_interface

另外如果std::ranges::range T继承std::ranges::view_interface, 那么在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类型的对象, 满足std::ranges::viewable_ranges, 并且当


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

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


2.2. 结构化解绑

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

auto [i, s] &#61; 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即可直接获得对象
  • 对象std::views::single, any -> view, 单对象的std::ranges::view
  • 对象std::views::iota, iterator | (iterator, sentinel) -> view, 一般哨位边界的有限或无限递增序列
  • 对象std::views::counted, (iterator, count) -> view, 计数哨位边界的有限递增序列
  • 对象std::views::istream, istream -> 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&#43;&#43;23), 重放
  • 对象std::views::cartesian_product(C&#43;&#43;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 & view -> input_range & view 展平
  • std::views::split(forward_range & view), forward_range & view -> subrange 序列中的指定子序列为分割点划分序列
  • std::views::common, view -> common_range 同化iterator_tsentinel_t的类型, 以兼容旧的库函数

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


3.3. operator|()的流式变换

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


  • 二段可调
    std::views::take, 一段调用auto c &#61; 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&#43;&#43;来实际上是std::ranges::range的概念
以kotlin为例 Flow, Channel, Sequence, Iterable的接口对比


-kotlin Iterator/SequenceC&#43;&#43;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&#43;&#43;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&#43;&#43;23)
过滤filterstd::views::filter
映射map
/
/
std::views::transform
std::views::zip_transform (C&#43;&#43;23)
std::views::adjacent_transform (C&#43;&#43;23)
组合zip
zipWithNext
/
/
std::views::zip (C&#43;&#43;23)
/
std::views::slide (C&#43;&#43;23)
std::views::adjacent (C&#43;&#43;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&#43;&#43;23)
std::views::chunk_by (C&#43;&#43;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([&#61;](auto&& it) {
return std::forward<decltype(it)>(it) &#43; n;
});
};

然后就可有

range | plus(123);

3.5.1. 常用的变换实现参考


  • with_index: 附加计数器

inline constexpr auto with_index(size_t start &#61; 0) {
return std::views::transform([index &#61; start](auto&& it) mutable {
return std::make_tuple(index&#43;&#43;, 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 &#61; std::forward<decltype(container)>(container)](auto&& it) {
return std::ranges::find(cont, it) &#61;&#61; std::ranges::end(cont);
});
}

用法

range | subtract(std::array{ v1, v2, v3 });

  • to: 输出到容器 (标准库版本在C&#43;&#43;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 &#61; std::array{1, 2, 3} | to(std::vector<int>{});

  • on_each_indexed: 附加计数的遍历

inline constexpr auto on_each_indexed(auto&& func, size_t start &#61; 0) {
return std::views::filter([index &#61; start, func &#61; std::forward<decltype(func)>(func)](auto& value) mutable {
func(index&#43;&#43;, 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 &#61; ptrdiff_t;
MyIter& operator&#43;&#43;() { return *this; }
MyIter operator&#43;&#43;(int) { return *this; }
int operator*() const { return 0; }
};

4.2. std::forward_iterator

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

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

4.3. std::semiregular

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

struct Type {};






推荐阅读
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 本文介绍了在实现了System.Collections.Generic.IDictionary接口的泛型字典类中如何使用foreach循环来枚举字典中的键值对。同时还讨论了非泛型字典类和泛型字典类在foreach循环中使用的不同类型,以及使用KeyValuePair类型在foreach循环中枚举泛型字典类的优势。阅读本文可以帮助您更好地理解泛型字典类的使用和性能优化。 ... [详细]
  • 如何优化Webpack打包后的代码分割
    本文介绍了如何通过优化Webpack的代码分割来减小打包后的文件大小。主要包括拆分业务逻辑代码和引入第三方包的代码、配置Webpack插件、异步代码的处理、代码分割重命名、配置vendors和cacheGroups等方面的内容。通过合理配置和优化,可以有效减小打包后的文件大小,提高应用的加载速度。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • Python教学练习二Python1-12练习二一、判断季节用户输入月份,判断这个月是哪个季节?3,4,5月----春 ... [详细]
  • 花瓣|目标值_Compose 动画边学边做夏日彩虹
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Compose动画边学边做-夏日彩虹相关的知识,希望对你有一定的参考价值。引言Comp ... [详细]
author-avatar
手机用户2502940425
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有