跳至主要内容

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的寫法有以下缺點:

  1. algorithm function 傳入的參數不直觀,且不同 algorithm回傳的數值有不同行為
    • 像是remove_if 會改變原本的vector v,並且把不符合條件的值放到後面,最後回傳一個end iterator,代表 [begin, end)的數值是合法的,之後都是不合法的。
  2. 語法繁雜,重複性高。
  3. 需要對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::filterviews::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可以使用,且未來也會推出更多的功能可以使用,值得期待!

Ref