Ranges / Views
Introduction
C++ 20 中推出了新的 Range library,可以讓我們在寫 C++ 時寫的更順手。
在開始前,需要先大概認識三個名詞:
- Range:
- 一系列的元素,像是 STL containers
- E.g. vector, list
- Adaptors
- 可以套用在 range/view 上的操作
- E.g. filter, transform
- View
- 一系列的元素,但是 view 不擁有其擁有權(不能對值進行操作)
- view 的操作必須是 constant time。
- 其中一種 view 為 range 加上 adaptors 後產生的 output
Examples
給定一群數字存在 vector v,先把裡面的元素 mod 2 == 1 的過濾掉,接著把剩下的值都乘以 5。
在c++20以前,我們可能會這樣寫:
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// If we can't modify v, may need to copy a new v.
// vector<int> copyv = v;
auto newEnd = remove_if(begin(v), end(v), [](int x)
{ return x % 2 == 1; });
// remove unused values
v.erase(newEnd, end(v));
transform(begin(v), end(v), begin(v), [](int x)
{ return x * 5; });
for (int &val : v)
{
cout << val << ' ';
}
可以發現這樣使用原本algorithm的寫法有以下缺點:
- algorithm function 傳入的參數不直觀,且不同 algorithm回傳的數值有不同行為
- 像是
remove_if
會改變原本的vector v,並且把不符合條件的值放到後面,最後回傳一個end iterator,代表 [begin, end)的數值是合法的,之後都是不合法的。
- 像是
- 語法繁雜,重複性高。
- 需要對vector直接做操作。若不想或無法直接對vector v做操作時,則可能需要額外花時間和空間複製vector v。
接著來看用了 c++20 ranges後的寫法:
#include<ranges>
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto oddFilter = [](int x) { return x % 2 == 0; };
auto multiply5 = [](int x) { return x * 5; };
auto myView = v | views::filter(oddFilter) | views::transform(multiply5);
for (int val : myView)
{
cout << val << ' ';
}
// Output: 10 20 30 40 50
在上面程式中,相對應到的名詞為:
- range: vector v
- adaptors:
views::filter
跟views::transform
- View: myView
可以看到寫法變成相當簡潔且直觀。而且如果給定的vector v為const,我們一樣可以進行操作,因為上面程式對vector v創建了view,而view並沒有擁有原本的vector v,也就無法對他進行改寫。
這裡也可以注意到 |
operator 在這裡被當作像是 linux pipeline 操作符的語法,讓我們可以銜接不同的 range/view 和 adaptors
在此例中,當我們 create myView
時,並不會真的去做計算。實際計算的時機為當我們跑 for 迴圈時,而且一次只會計算部分的元素,並不是一次把myView
該顯示的值全部算好後再拿到 for 迴圈。
這個又叫做 lazy loading。
Convert range/view to container
假如最後產生的view我們不只想iterate他們,而是想轉成STL並在對他做後續操作,則可以用 ranges::to
function做到。不過這是 c++23的語法了。
vector<int> filter_v = myView | ranges::to<vector>();
Algorithms
一般在用 STL algorithm 時,常常會發現需要打很多多餘的字 E.g.
vector<int> v = {11, 2, 43, 46, 5, 10};
sort(begin(v), end(v));
現在多了 range library,可以直接寫成
ranges::sort(v);
意思是相同的!但是少寫了很多字
其他類似的 algorithm 也都有實作了 range 版本的 algorithm。詳細可以查看 cpp reference。
Summary
C++20新增的ranges library讓developer在對container進行操作時能夠寫出更直觀、簡潔的且有效率的程式。在cpp standard還有更多的range / adaptors可以使用,且未來也會推出更多的功能可以使用,值得期待!