C++/ranges
<ranges>是C++20引入的標準庫。<ranges>中定義了std::ranges和std::views命名空間。
「範圍」(range)是一個可以迭代的對象的集合,支持begin()和end()迭代器的結構都是範圍。這包括大多數STL容器。
「視圖」(view)從底層的範圍返回數據,但不擁有任何數據,並通過某種算法或操作轉換底層的範圍。視圖是惰性的,只在範圍迭代(即請求元素)時才會惰性求值,而不是在創建視圖時執行。view是容器的視圖,具有一定的數據操作能力,而且還重載了"|",能夠實現多個view的串聯操作。
視圖適配器是一個對象,可接受一個範圍,並返回一個視圖對象。視圖適配器可以使用|操作符連接到其他視圖適配器。視圖適配器從|操作符的左側獲得範圍操作數。|操作符會從左到右求值。視圖適配器位於std::views命名空間中。
一個簡單例子:
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int>vi{0,1,2,3,4,5,6,7,8,9};
auto tview =vi|std::views::reverse|std::views::take(5); //视图适配器
for(int i:tview)
std::cout<<i<<" ";
//输出为:9 8 7 6 5
// 不会产生数据
auto view = std::views::iota(0, 10);
// 开始产生数据
for (auto i : view)
{
std::cout << i << std::endl;
}
return 0;
}
ranges庫在std::views命名空間中包括一些變換函數:
- take(int)選出view里的前N個元素。如果改編後的視圖包含的元素少於N,則返回所有元素。
- take_while()給定一個一元謂詞 pred 和一個視圖 r,它生成一個範圍 [begin(r), ranges::find_if_not(r, pred)) 的視圖。
- reverse()逆序雙向視圖裏的所有元素
- filter(invokable)視圖使用一個謂詞函數篩選出view里的特定元素,例如
auto result = nums|std::views::filter([](int i){return 0==i%2;});
- transform(invokable)視圖使用了一個轉換函數,對每個元素應用轉換函數後,返回底層序列的視圖。例如
auto result =nums|std::views::transform([](int i){return i*i;});
- drop()排除view里的前N個元素,如果改編後的視圖包含少於 N 個元素,則返回一個空範圍。例如:
rnums = std::views::drop(rnums,5);
- drop_while()給定一個一元謂詞 pred 和一個視圖 r,它生成一個範圍 [ranges::find_if_not(r, pred), ranges::end(r)) 的視圖。
- keys()選出pair里的first成員。是elements_view<views::all_t<R>, 0> 的別名。
- values()選出pair里的second成員。是elements_view<views::all_t<R>, 1>的別名。
- all() 返回一個包含傳入的 range 參數的所有元素的視圖
- join()將一個範圍視圖平展為一個視圖
- split(forward_range & view)接受一個視圖和一個分隔符,並將視圖劃分為分隔符上的子範圍。分隔符可以是單個元素,也可以是元素的視圖。
- common()或common_range()獲取一個迭代器和哨兵具有不同類型的視圖,並將其轉換為具有相同類型迭代器和哨兵的相同元素的視圖。對於調用期望範圍的迭代器和哨點類型相同的遺留算法很有用。
- counted()計數視圖顯示了迭代器i和非負整數 n 的計數範圍([iterator.requirements.general]) i+[0, n) 的元素的視圖。
- elements()接受一個類元組值和 size_t 的視圖,並生成一個值類型為已改編視圖值類型的第 n 個元素的視圖。
- zip(C++23) 由對已改編視圖的相應元素的引用的元組組成的視圖
- zip_transform(C++23) 由轉換函數應用到所適應視圖的相應元素的結果元組組成的視圖
- adjacent(C++23) 由對已改編視圖的相鄰元素的引用元組組成的視圖
- adjacent_transform(C++23) 由轉換函數應用於所適應視圖的相鄰元素的結果元組組成
- join_with(C++23) 一種視圖,由將範圍視圖平展得到的序列組成,元素之間有分隔符
- slide(C++23) 第 M 個元素是另一個視圖的第 M 個到 (M + N - 1) 個元素的視圖
- chunk(C++23) 一個由另一個視圖的元素組成的n個大小的不重疊連續塊的視圖範圍
- chunk_by(C++23) 將視圖拆分為給定謂詞返回false的每對相鄰元素之間的子範圍
- as_const(C++23) 將視圖轉換為常量範圍
- as_rvalue(C++23) 將每個元素強制轉換為右值的序列視圖
- stride(C++23) 由另一個視圖的元素組成的視圖,一次向前移動N個元素
ranges庫還包括一些factory函數:
- 對象std::views::empty, void -> view, 使用std::views::empty<T>即可直接獲得對象
- 對象std::views::single, any -> view, 單對象的std::ranges::view
- 對象std::views::iota, iterator | (iterator, sentinel) -> view, 一般哨位邊界的有限或無限遞增序列 例如,itoa將生成一系列遞增的值,如
auto rnums =std::views::iota(1,10);
- 對象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), 由n元笛卡爾積計算出的結果元組組成的視圖
range是容器上的一個抽象概念,可以理解成指明首末位置的迭代器,即pair<begin,end>,這樣range自身就包含能用於算法的足夠信息,大多數算法只要用一個range參數就可以工作。基於range的概念,C++在名字空間std::ranges提供了與標準算法同名、但卻使用range參數的算法,寫法很簡潔。從C++20開始,<algorithm>頭文件中的大多數算法都會基於「範圍」。這些版本在<algorithm>頭文件中,但是在std::ranges命名空間中,這使它們與傳統算法分離開。所以無需再調用兩個迭代器的算法。例如std::sort(v.begin(),v.end());
可以用範圍來調用std::ranges::sort(v);
使用試圖適配器更為直觀、易讀:std::ranges::sort(v|std::ranges::view::reverse|std::ranges::views::drop(5));
range的分類
編輯Concept | Description |
---|---|
std::ranges::input_range | can be iterated from beginning to end at least once |
std::ranges::forward_range | can be iterated from beginning to end multiple times |
std::ranges::bidirectional_range | iterator can also move backwards with --
|
std::ranges::random_access_range | can jump to elements in constant-time []
|
std::ranges::contiguous_range | elements are always stored consecutively in memory |
std::ranges::sized_range | 範圍的大小可由std::ranges::size()獲得,提供size函數,能常數時間獲取範圍的長度;如果未提供size函數, 那麼還要求
std::forward_iterator且iterator與sentinel(及iterator)可作差;如果size或者iterator的作差不能用常數時間實現, 那麼可以用特化:std::ranges::disable_sized_range<T> = true且std::disable_sized_sentinel_for<iterator, iterator> = true, 強制關閉std::ranges::sized_range和std::ranges::sized_sentinel_for的特徵 |
std::ranges::common_range | 如果iterator == sentinel |
std::ranges::viewable_range | 如果std::ranges::view 或std::ranges::borrowed_range |
std::ranges::borrowed_range | 如果類型std::ranges::range T的值T t的t.begin()和t.end()獲得的迭代器的生命周期與t無關, 那麼可以認為是std::ranges::borrowed_range。由於語言層面無法自動識別生命周期的關係, 因此要特徵能被識別, 還要手動特化std::ranges::enable_borrowed_range<T>為true |