阅读Eric Niebler的
我遇到了术语Sentinel作为最终迭代器的替代。
我很难理解哨兵在终点迭代器上的好处。
有人可以提供一个明确的示例,说明sendintel带来了用标准迭代配对完成的桌子吗?
“ A 前哨是过去迭代器的抽象。前哨是
可以用来表示范围末端的常规类型。一个
Sentinel和表示范围的迭代器应相等。
当迭代器我比较等于
Sentinel,我指向该元素。” - N4382
我认为哨兵在确定范围的末端而不是位置?
While reading Eric Niebler's range proposal,
I've come across the term sentinel as replacement for the end iterator.
I'm having a difficult time understanding the benefits of sentinel over an end iterator.
Could someone provide a clear example of what sentintel brings to the table that cannot be done with standard iterator pairs?
"A sentinel is an abstraction of a past-the-end iterator. Sentinels are
Regular types that can be used to denote the end of a range. A
sentinel and an iterator denoting a range shall be EqualityComparable.
A sentinel denotes an element when an iterator i compares equal to the
sentinel, and i points to that element." -- N4382
I think sentinels work as functions in determining the end of a range, instead of just the position?
发布评论
评论(3)
Sentinel仅允许最终迭代器具有不同的类型。
过去的迭代器上允许的操作有限,但这并不反映在其类型中。
*
a.end()
迭代器是不可能的,但是编译器会让您。哨兵没有一个单位解除,或
++
等。通常,它与最弱的迭代器一样受到限制,但在编译时执行。有回报。通常,检测终端比找到终端更容易。使用Sentinel,
==
可以在编译时间(而不是运行时间)派遣“检测另一个参数超过结束”。结果是,一些过去比C当量慢的代码现在将其编译为C级速度,例如使用
std :: copy
复制零终止字符串。没有哨兵,您要么必须扫描以在副本前找到末端,要么带有布尔标志的迭代器,说“我是末端前哨”(或等效),然后对其进行检查==
。在使用基于计数的范围时,还有其他类似的优势。此外,诸如邮政编码 1 之类的某些事情变得更易于表达(端Zip Sentinel可以容纳两个源前哨,如果任何一个前哨使用,则返回相等:两个都)。
考虑它的另一种思考方式是,算法倾向于不使用迭代器概念的全部参数,因为该参数通过过去的终结迭代仪,并且在实践中以不同的方式处理了迭代器。 Sentinel表示呼叫者可以利用该事实,这又使编译器更容易利用它。
1 当您从2个或更多范围开始时,您可以获得邮政范围,并且像拉链一样将它们一起“拉链”。现在,该范围位于各个范围元素的元素上。推进拉链迭代器将推进每个“包含”迭代器,并同上用于删除和比较。
Sentinel simply allows the end iterator to have a different type.
The allowed operations on a past-the-end iterator are limited, but this is not reflected in its type. It is not ok to
*
a.end()
iterator, but the compiler will let you.A sentinel does not have unary dereference, or
++
, among other things. It is generally as restricted as the weakest iterators one past the end iterator, but enforced at compile time.There is a payoff. Often detecting the end state is easier than finding it. With a sentinel,
==
can dispatch to "detect if the other argument is past the end" at compile time, instead of run time.The result is that some code that used to be slower than the C equivalent now compiles down to C level speed, such as copying a null terminated string using
std::copy
. Without sentinels, you either had to scan to find the end before the copy, or pass in iterators with a bool flag saying "I am the end sentinel" (or equivalent), and check it on==
.There are other similar advantages when working with counting based ranges. In addition, some things like zip ranges1 become easier to express (the end zip sentinel could hold both source sentinels, and return equal if either sentinel does: zip iterators either only compare the first iterator, or compare both).
Another way of thinking about it is that algorithms tend to not use the full richness of the iterator concept on the parameter passed as the past the end iterator, and that iterator is handled way differently in practice. Sentinel means that the caller can exploit that fact, which in turn lets the compiler exploit it easier.
1 A zip range is what you get when you start with 2 or more ranges, and "zip" them together like a zipper. The range is now over tuples of the individual range elements. Advancing a zip iterator advances each of the "contained" iterators, and ditto for dereferencing and comparing.
引入哨兵的主要动机是,有很多迭代器操作得到了支持,但通常不需要最终介绍
end> end()
。例如,通过*end()
,通过++ end End()
等,几乎没有任何意义。相反,
end()
的主要用法仅仅是将其与迭代器it
进行比较,以便向it
发出信号它只是迭代的东西。而且,与往常一样,在编程中,不同的要求和不同的应用程序提出了一种新类型。范围V3库将此观察结果变成一个假设(通过一个概念实现):它引入了
end()
>的新类型,并且仅要求它与相应的迭代器相等, - 但不需要通常的迭代器操作)。这种新类型的end()
称为 sentinel 。这里的主要优点是获得的抽象和更好的关注点,基于编译器可以执行更好的优化。在代码中,基本思想是这个(这仅是用于解释,与范围V3库无关):
请参阅抽象? is_at_end 函数中实现您想要的任何
\ 0
时,即*IT ='\ 0'
(用于通过C-sining循环时)此外,关于性能,可以在检查中使用编译时信息(例如,将上面的
n
视为编译时参数)。在这种情况下,编译器可能能够更好地优化代码。(*)请注意,这并不意味着这种操作一般没有用。例如,
- end()
在某些地方可能很有用,请参见eg 这个问题。但是,如果没有这些,似乎可以实现标准库 - 这就是范围V3库所做的。The central motivation for introducing a sentinel is that there are a lot of iterator operations which are supported but usually never needed for the end-iterator
end()
. For example, there is hardly any point in dereferencing it via*end()
, in incrementing it via++end()
, and so on (*).In contrast, the main usage of
end()
is merely to compare it with an iteratorit
in order to signalize whetherit
is at the end of the thing it just iterates. And, as usual in programming, different requirements and different application suggest a new type.The range-v3 library turns this observation into an assumption (that is implemented through a concept): it introduces a new type for
end()
and only requires that it is equality-comparable with the corresponding iterator -- but does not require the usual iterator operations). This new type ofend()
is called a sentinel.The main advantage here is the gained abstraction and better separation of concerns, based on which the compiler is possibly able to perform a better optimization. In code, the basic idea is this (this is just for explanation and has nothing to do with the range-v3 library):
See the abstraction? Now, you can implement any check you want in the
is_at_end
function, e.g.:N
increments (to get a counted range)\0
is encountered, i.e.*it = '\0'
(for looping over C-strings)Moreover, regarding performance, one is able to make use of compile time-information in the check (e.g., think of the
N
above as a compile-time parameter). In this case, the compiler might possibly be able to better optimize the code.(*) Note that this does not mean there is in general no use for this kind of operations. For example,
--end()
can be useful in some places, see e.g. this question. However, it is seemingly possible to implement the standard library without these -- this is what the range-v3 library has done.哨兵和终端迭代器相似,因为它们标志着范围的末端。它们在检测到的结局方面有所不同。您要么测试迭代器本身,要么在迭代器中测试数据值。如果您已经在数据上执行测试,则哨兵可以允许您的算法“免费”完成,而无需任何其他测试。这可以简化代码,也可以使其更快。
一个非常常见的前哨是用于标记字符串末端的零字节。无需在字符串的末端保留单独的迭代器,可以在与字符串本身的字符合作时确定。此公约的缺点是字符串不能包含零字符。
请注意,在阅读链接中的提案之前,我写了这个答案。这是哨兵的经典定义,它可能与那里提出的定义不一致。
Sentinels and end iterators are similar in that they mark the end of a range. They differ in how that end is detected; either you're testing the iterator itself, or you're testing the data value at the iterator. If you're already performing tests on the data, a sentinel can allow your algorithm to finish "for free" without any additional tests. This can either simplify the code, or make it faster.
A very common sentinel is the zero byte that is used to mark the end of a string. There's no need to keep a separate iterator for the end of the string, it can be determined as you work with the characters of the string itself. The downside of this convention is that a string cannot contain a zero character.
Note that I wrote this answer before reading the proposal in the link; this is the classic definition of a sentinel which may not agree with the definition proposed there.