c ++20 几种新的迭代器讲解

498 阅读3分钟

新的迭代器

为了(更好地)支持范围和视图,C++20 引入了几种新的迭代器类型:

  • std::counted_iterator 用于迭代器,该迭代器本身具有指定范围结束的计数

- std::common_iterator 用于可用于具有不同类型的两个迭代器的通用迭代器类型

- std::d efault_sentinel_t 用于强制迭代器检查其结束的结束迭代器

  • std::unreachable_sentinel_t 用于永远无法达到的结束迭代器,因此范围是无限的

std::counted_iterator

计数迭代器是一个迭代器,它有一个计数器来表示要迭代的最大元素数。

有两种方法可以使用这样的迭代器:

  • 在检查剩余元素数量时进行迭代
for (std::counted_iterator pos{coll.begin(),5}; pos.count()>0;++pos){ 

    std::cout <<*pos <<'\n';

}

std::cout <<'\n';
  • 在与默认哨兵比较时进行迭代:
for (std::counted_iterator pos{coll.begin(),5}; 

    pos != std::default_sentinel; ++pos) { 
    
    std::cout<<*pos<<'\n';

}

当视图适配器 std::ranges::counted() 为不是随机访问迭代器的迭代器生成子范围时,将使用此功能。

  • 初始计数不高于最初传递的迭代器
  • 计数迭代器的增量不会超过计数次数
  • 计数迭代器不会访问第 count 第 个元素之外的元素(像往常一样,最后一个元素后面的位置是有效的)

相关内容可以参考 : iterator

std::common_iterator

类型std::common iterator<>用于协调两个迭代器的类型。

它包装了两个迭代器,从外部看,两个迭代器具有相同的类型。在内部存储两种类型之一的值(为此迭代器通常使用std::variant<>)。

例如,如果采用开始迭代器和结束迭代器的传统算法不支持这些迭代器具有不同的类型,则可以使用此类型来启用调用:

algo(beg,end); // if this is an error due to different types

algo(std::common_iterator<decltype(beg), decltype(end)>{beg},//OK

std::common_iterator<decltype(beg),decltype(end)>{end});

请注意,如果传递给common_iterator<>的类型相同,则为编译时错误。因此,在泛型代码中,您可能必须调用:

template<typename TBeg,typename TEnd>

void callAlgo(TBeg beg, TEnd end)

{

if constexpr(std::same_as<TBeg,TEnd>) {

    algo(beg,end);}

else{

        algo(std::common_iterator<decltype(beg), decltype(end)>{beg},

            std::common_iterator<decltype(beg),decltype(end)>{end});

    }

}

获得相同效果的更方便方法是使用 common()适配器:

template<typename TBeg,typename TEnd>

void callAlgo(TBeg beg, TEnd end) 
{

    auto v = std::views::common(std::ranges::subrange(beg,end)); 

    algo(v.begin(), v.end());

}

std::default_sentinel

defuult 哨兵是一个根本不提供任何操作的迭代器。C++20 提供了一个对应的对象 std::d efault_sentinel,其类型为 std::d efault_sentinel_t <iterator>。该类型没有成员:

namespace std {

	class default_sentinel_t { 

	};
	
	inline constexpr default_sentinel_t default_sentinel{}; 

}

提供类型和值以用作虚拟哨兵(结束迭代器),当迭代器知道范围何时结束时使用。对于这些迭代器,与std::d efault_sentinel的比较会触发迭代器的内部检查,无论它是在末尾还是离它有多远。

例如,计数迭代器为自己定义一个运算符 ==,并使用默认哨兵来执行检查它们是否在末尾:

namespace std{

	template<std: :input_or_output_iterator I> 
	
	class counted iterator {
	...

	friend constexpr bool operator==(const counted_iterator& p,

									std::default_sentinel_t){ 
									
		...// 返回 p 在末尾

		} 
	};
 }

这允许如下代码:

//迭代前 5 个元素:

for (std: :counted_iterator pos{coll.begin(),5}; 
	
		pos !=std::default_sentinel;

		++pos){

	std::cout <<*pos <<'\n';
}

该标准使用默认哨兵定义了以下操作:

  • 对于std:: counted_iterator:

    • 与运算符的比较 ==!=
    • 计算与运算符的距离
  • std::views:: counts()可以创建计数迭代器和默认哨兵的子范围

  • 对于 std::istream_iterator:

    • default sentinels可以用作初始值,其效果与默认构造函数相同
    • 与运算符==!=进行比较
  • 对于 std::istreambuf_iterator:

    • 默认哨兵可以用作初始值,其效果与默认构造函数相同
    • 与运算符==!=进行比较
  • 对于 std::ranges::basic_istream_view<>:

    • Istream 视图产生 std::d efault_sentinel 作为结束
    • -Istream 视图迭代器可以与运算符 ==and != 进行比较
  • 对于 std::ranges::take_view<>:

    • 视图可以生成 std: : default _ Sentinel 作为 end ()
  • 对于 std::ranges::split_view<>:

    • 拆分视图可能会产生 std::d efault_sentinel 作为 end()
    • 拆分视图迭代器可以与运算符进行比较==and!=

std::unreachable_sentinel

C++20 中引入了 std::unreachable_sentinel_t 类型的值 std::unreachable_sentinel,用于指定无法访问的哨兵(范围的结束迭代器)。它实际上说“'根本不要和我比较。效果是它可用于指定无限范围。

当使用它时,它可以优化生成的代码,因为编译器可以检测到将其与另一个迭代器进行比较永远不会产生 true,因此它可能会跳过任何针对末尾的检查。

例如,如果我们知道集合中存在值 42,我们可以按如下方式搜索它:

auto pos42 = std::ranges::find(coll.begin(),std::unreachable_sentinel,

42);

通常,算法会与 42 和 co11.end()(或任何作为 end/sen- tinel 传递的内容)进行比较。由于使用了std:unreachable_sentinel,因此与迭代器的任何比较总是产生 false,因此编译器可以优化代码以仅与 42 进行比较。

iota view提供了一个在无限范围内使用不可达哨兵的示例。


开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情