【问题标题】:What is the point of c++20 ranges? [closed]c++20 范围的意义何在? [关闭]
【发布时间】:2021-08-06 00:26:21
【问题描述】:

与老式的迭代器相比,我很难理解 c++20 范围增加了什么。是的,我想不再需要使用beginend,而是简单的重载,例如:

namespace std {
    template <typename Container>
    auto transform(Container&& container, auto&&... args) requires ( requires {container.begin(); container.end(); }) {
         return transform(container.begin(), container.end(), args...);
    }
}

会解决这个问题。

与迭代器相比,范围为什么有用以及何时应该使用它们?

编辑:我知道范围比迭代器有其他优势(链接、更好的方法等......)。然而,这些(我认为?)都可以用迭代器来完成,我不明白为什么需要引入一个全新的概念,比如范围。

【问题讨论】:

  • 这是一个很好的问题,我自己也在努力寻找答案。
  • “也可以用迭代器完成” 但不太方便。按照同样的逻辑,我们应该用纯 C 编写,因为它可以做 C++ 可以做的任何事情,等等。
  • @SomeProgrammer 不,你不能。因为单个迭代器只是信息的一半。您不能链接迭代器。 it | transform(f) 为您提供了一个适配的迭代器,但您还需要适配 end 迭代器才能执行任何操作。能够做任何事情是不够的。想象一下这看起来像...... filter
  • @SomeProgrammer 哦,所以你是说你需要一个范围?
  • @SomeProgrammer: "我认为你可以重载管道操作符,以便迭代器可以像范围一样链接方法。" 如果是这种情况,请提供一些证据.在您的问题中添加一些代码,以了解链接和其他范围操作如何在普通迭代器上工作。

标签: c++ iterator c++20 std-ranges


【解决方案1】:

您已经反对自己的结论,如下所示:

template <typename Container>
auto transform(Container&& container, auto&&... args)
  requires ( requires {container.begin(); container.end(); }) {

那么……这是什么?它是一个接受满足约束的模板参数的函数。让我们忽略此约束要求成员 begin/end 而不是更合理的 std::ranges::begin/end 要求。

您要将此要求应用于多少个函数?应该很多吧每个算法都会有一个有这个要求的版本。所以这开始看起来不像一次性的要求,而更像是应该命名为 concept 的东西。

特别是因为这个概念可能应该指定算法需要什么样的迭代器。您不仅需要会员begin/end;你需要他们返回一个 input_or_output_iterator 和一个 sentinel_for 那个迭代器:

requires ( requires(Container c)
{
  {c.begin()} -> input_or_output_iterator;
  {c.end()} -> sentinel_for<decltype(c.begin())>;
})

您真的要在每次请求“容器”时输入这个吗?当然不是;这就是命名概念的用途。

那么这个概念是什么?这是一个可以迭代的东西,一个可以通过特定迭代器接口访问的值序列。

并且应该选择该概念的名称,以免暗示对元素序列的所有权。 transform 算法不关心给定的内容是否拥有该序列。所以“容器”绝对是错误的名称。

所以我们称这个概念为 rangevalue-sequence。值序列可以通过迭代器/哨兵接口进行迭代。而且您可能需要拥有不同类别的值序列。输入序列、前向序列、连续序列等。您可能想要检测序列是否可以在恒定时间内计算大小,或者序列是否有界或从其所有者借来的。

如果您可以编写运算符来创建这些值序列的视图,那不是很好吗?

任何其他名称的系列闻起来都一样甜美。一旦你开始了将迭代器与哨兵配对的黑暗道路,它将永远主宰你的命运。

在处理迭代器时,范围是一个很自然的概念。一切都建立在范围概念之上

【讨论】:

    【解决方案2】:

    大多数标准库核心概念(例如迭代器)的重点是统一标准库中常用的抽象。对于迭代器,这意味着为“this 指向容器中的一个元素,我们希望能够迭代容器”这一常用概念提供一个接口。

    那么,范围的意义在于从公共用户界面中隐藏原始迭代器。很多时候迭代时,我们需要 2 个指针;我们容器的开始和结束。 Ranges 试图通过隐藏这个接口来简化这一点,并为在 begin() 和 end() 之间操作的函数提供单一接口。

    特别是,范围视图将是使用范围的主要原因。当您想要进行函数组合时,它们允许更容易阅读代码。 cppreference 的例子是一个很好的例子:

    #include <ranges>
    #include <iostream>
     
    int main()
    {
        auto const ints = {0,1,2,3,4,5};
        auto even = [](int i) { return 0 == i % 2; };
        auto square = [](int i) { return i * i; };
     
        // "pipe" syntax of composing the views:
        for (int i : ints | std::views::filter(even) | std::views::transform(square)) {
            std::cout << i << ' ';
        }
     
        std::cout << '\n';
     
        // a traditional "functional" composing syntax:
        for (int i : std::views::transform(std::views::filter(ints, even), square)) {
            std::cout << i << ' ';
        }
    }
    

    与原始迭代器相比,范围和视图以接近零的成本提供更高级别的抽象层。在我个人看来,与使用没有范围的 c++ 编写的代码相比,特别是“视图管道”更易于阅读和维护。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-05-18
      • 2021-01-14
      • 2022-01-25
      • 1970-01-01
      • 2015-03-22
      • 2015-01-10
      • 2012-10-17
      • 1970-01-01
      相关资源
      最近更新 更多