范围库的组成

Ranges可以理解为迭代器更上一层的抽象,封装提供了更高级的操作。Ranges有如下组成部分:

  • range:range是一个concept,用于一个类型迭代访问自己的数据。range表示一个范围,支持begin()end()的类型就是一个range。比如array,vector,C数组等
  • Range-based algorithms:算法库使用迭代器访问容器数据,C++20开始也能接受range作为参数来访问容器。
  • Projection:很多range算法有projection投影回调参数,投影回调会作用于容器的每个元素,在开始算法前对容器数据进行一些转换操作。
  • VIews:view用于转换或过滤底层range中的数据,是容器数据的一个视图。通过视图可以对数据进行访问个修改。
  • Factories:factory用于构建view

    range algorithm

    这些算法函数在**std::ranges**命名空间下,接受range变量作为参数 ```cpp vector data { 33, 11, 22 }; sort(begin(data), end(data));//普通算法使用迭代器,需要传递begin/end

ranges::sort(data);//range based算法使用range,vector就是一个range,所以直接传给函数

  1. <a name="KeC8q"></a>
  2. # projection
  3. 看一个投影的例子:
  4. ```cpp
  5. class Person
  6. {
  7. public:
  8. Person(string first, string last)
  9. : m_firstName{move(first)}, m_lastName{move(last)} {}
  10. const string &getFirstName() const { return m_firstName; }
  11. const string &getLastName() const { return m_lastName; }
  12. private:
  13. string m_firstName;
  14. string m_lastName;
  15. };
  16. vector persons { Person {"John", "White"}, Person {"Chris", "Blue"} };
  17. //尝试进行排序
  18. sort(begin(persons), end(persons)); // Error: does not compile
  19. ranges::sort(persons); // Error: does not compile

使用vector保存了Person类的对象,但是因为Person没有实现比较运算符(<)的重载,所以编译器不知道如何比较Person的大小,无法进行排序。
所以使用映射,在开始排序时,取出每个对象的firstName作为排序的依据:

  1. //使用映射,告诉sort使用什么规则判断Person大小
  2. ranges::sort(persons, {}, &Person::getFirstName);
  3. //第二个参数表示使用什么排序函数,这里不改

view

view有以下特点:

  • 对view的操作不会影响底层的range数据,也就不会影响容器被原始数据
  • view没有数据的所有权,仅仅是查看数据的另一种方式(参照一下数据库的视图)
  • view也是一个range
  • 标准容器是range,但不是view,因为容器拥有数据的所有权

    range adapter

    range adapter接受range或view,加上其他参数,然后返回一个新的view对象。
    常见的range adapter如下:
    image.png

    创建view

    可以看到上面的adapter都是成对出现的,对应两种创建view的方式:

  • auto v = std::ranges::xxx_view { range, arguments... };

  • auto v = range | std::ranges::views::xxx(arguments...);

使用view的示例:

  1. //使用range adapter创建view,不同的adapter代表不同的操作
  2. #include <algorithm>
  3. #include <iostream>
  4. #include <ranges>
  5. #include <string>
  6. #include <vector>
  7. using namespace std;
  8. void printRange(string_view message, auto& range)
  9. {
  10. cout << message;
  11. for (const auto& value : range) {
  12. cout << value << " ";
  13. }
  14. cout << endl;
  15. }
  16. int main()
  17. {
  18. vector values{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  19. printRange("原始容器数据: ", values);
  20. //创建filter view,并设置筛选函数
  21. auto result1{ values | views::filter([](const auto& value) { return value % 2 == 0; }) };
  22. printRange("filter view values: ", result1);
  23. //创建transform view,修改view数据
  24. auto result2{ result1 | views::transform([](const auto& value) { return value * 2.0; }) };
  25. printRange("transform view,Values doubled: ", result2);
  26. // drop view,跳过前两个数据
  27. auto result3{ result2 | views::drop(2) };
  28. printRange("Drop view,First two dropped: ", result3);
  29. // Reverse view. 反向迭代
  30. auto result4{ result3 | views::reverse };
  31. printRange("Reverse view: ", result4);
  32. //view之后看看原始数据
  33. printRange("view后容器数据: ", values);
  34. }

通过view修改原始数据

前面说过,view本身的创建和操作不会影响到range中的原始数据。不过我们依然可以通过view手动的修改view,view会将修改传递到容器中。要求range和view不能是只读的,要有可写权限。
比如上面例子,我们先去掉transform view,因为它是一个只读的view,会导致最终的result4也是只读的。然后如果手动去修改result4中的数据,vector中相应位置的数据也会被修改:

  1. vector values{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  2. printRange("原始容器数据: ", values);
  3. //创建filter view,并设置筛选函数
  4. auto result1{ values | views::filter([](const auto& value) { return value % 2 == 0; }) };
  5. printRange("filter view values: ", result1);
  6. // drop view,跳过前两个数据
  7. auto result3{ result1 | views::drop(2) };
  8. printRange("Drop view,First two dropped: ", result3);
  9. // Reverse view. 反向迭代
  10. auto result4{ result3 | views::reverse };
  11. printRange("Reverse view: ", result4);
  12. // 通过view result4修改数据
  13. for (auto& value : result4) { value *= 10; }
  14. printRange("After modifying elements through a view, vector contains: ", values);

最终根据result4中数据在容器中的位置,修改容器原始数据,结果为:
Reverse view: 10 8 6
vector contains: 1 2 3 4 5 60 7 80 9 100

Factories

range库提供了很多工厂用于创建view。和adapter的区别是,工厂是从头创建view,adapter是从range中直接获取。
image.png
示例:

  1. auto values { views::iota(10) };//创建一个从10开始自增的view,没有使用adapter
  2. // 在view基础上使用adapter创建filter view
  3. auto result1 { values | views::filter([](const auto& value) { return value % 2 == 0; })};
  4. // transform view
  5. auto result2 { result1 | views::transform([](const auto& value) { return value * 2.0; })};
  6. // 取前10个数据,结果为20 24 28 32 36 40 44 48 52 56
  7. auto result3 { result2 | views::take(10) };