[英]Why is std::common_iterator just std::forward_iterator?
C++20 引入了一個std::common_iterator
,它能夠將元素的非公共范圍(迭代器和哨兵的類型不同)表示為公共范圍(它們相同),其概要定義為:
template<input_or_output_iterator I, sentinel_for<I> S>
requires (!same_as<I, S> && copyable<I>)
class common_iterator {
// ...
private:
variant<I, S> v_; // exposition only
};
它對於與期望范圍的開始和結束具有相同類型的遺留代碼交互很有用。
在[iterators.common#common.iter.types-1.1]中,它的iterator_concept
定義為:
如果
I
forward_iterator
建模,iterator_concept
表示forward_iterator_tag
; 否則它表示input_iterator_tag
。
為什么common_iterator
最多只能是一個forward_iterator
,不能完全根據I
的iterator_category
iterator_concept
例如,如果I
是random_asscess_iterator
,那么common_iterator<I, S>
是random_asscess_iterator
,依此類推。
似乎這在技術上是可行的,因為common_iterator
只使用std::variant
來鍵入 erase I
和S
。
考慮以下( godbolt ):
auto r = views::iota(0) | std::views::take(5);
static_assert( ranges::random_access_range<decltype(r)>);
auto cr = r | views::common;
static_assert(!ranges::random_access_range<decltype(cr)>);
static_assert( ranges::forward_range<decltype(cr)>);
r
是random_access_range
,因此 C++20 約束算法如ranges::binary_search
可以使用此 trait 對其執行更高效的操作,但為了使舊的std
算法能夠應用它,我們需要使用views::common
將其轉換為common_range
。 但是,它也會退化為forward_range
,這會降低算法的效率。
為什么標准最多只定義common_iterator
為forward_iterator
? 這背后的考慮是什么?
如果你有一個非常規范圍的迭代器,並且你需要將它轉換成一個公共范圍,那么你基本上有兩種選擇。
您可以通過連續遞增開始迭代器直到它等於哨兵來計算結束迭代器的值,或者您可以做一些詭計。 common_iterator
用於后者。
這很重要,因為連續遞增開始迭代器有兩個缺陷。 首先,如果它至少不是一個向前的范圍,你就不能這樣做。 第二......如果范圍是無限的會發生什么? 因為那是 C++20 范圍內的事情。 事實上,這種可能性是我們擁有哨兵類型的最重要原因之一。
那么詭計多端。 然而,在這種情況下,“詭計”意味着創建一個新的迭代器類型,用於開始和哨兵。 因此,它必須具有這兩個方面的局限性。 哨兵基本上必須假裝它是一個迭代器。
迭代器可以遞增; 哨兵不能。 但是,無論如何,您都不允許增加范圍的結束迭代器,因此您可以假裝允許增加結束common_iterator
。 同樣,您不能取消引用哨兵,但也不能取消引用結束迭代器。 所以它可以假裝它可以被取消引用,即使沒有算法會這樣做。
這意味着對於前向范圍,除了針對其他迭代器測試結束迭代器之外,您不能做任何事情。 簡而言之,對於前向范圍,結束迭代器也可以是哨兵。
但對於更高級別的范圍,情況並非如此。 明確允許采用雙向范圍的算法向后移動結束迭代器。 但是你不能對假裝它是迭代器的哨兵這樣做。
這就是為什么common_iterator
范圍不能高於向前范圍的原因。
正如@daves 提到的,結束迭代器是一個迭代器。
如果您的迭代器支持--
(就像所有比 forward 更強大的東西一樣),那么您的通用迭代器必須能夠--
從包裝的哨兵。
哨兵不支持--
; 你會以某種方式偽造它。 在許多情況下,這需要 O(n) 的工作; 基本上掃描哨兵並將其變成迭代器。 這太糟糕了。
哨兵的概念與其他語言中已知的迭代器密切相關,它支持推進和測試您是否到達終點。 一個很好的例子是一個以零結尾的字符串,當你到達\0
時你會停下來,但事先不知道大小。
我的假設是,將其建模為std::forward_iterator
對於需要將 C++20 迭代器與哨兵轉換以調用舊算法的用例就足夠了。
我還認為應該有可能提供一個通用實現來檢測迭代器提供更多功能的情況。 它會使標准庫中的實現復雜化,也許這是反對它的論點。 在通用代碼中,您仍然可以自己檢測特殊情況以避免包裝隨機訪問迭代器。
但據我了解,如果您處理性能關鍵代碼部分,除非需要,否則應小心將所有內容包裝為std::common_iterator
。 如果底層variant
引入了一些開銷,我不會感到驚訝。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.