c++20 范围的意义是什么?

问题描述

与好的老式迭代器相比,我很难理解 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...);
    }
}

解决这个问题。

为什么范围有用?与迭代器相比,我应该在什么时候使用它们?

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

解决方法

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

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 算法不关心它给定的内容是否拥有序列。所以“容器”绝对是错误的名称。

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

如果您可以编写操作符来创建这些值序列的视图,岂不是很好?

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

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

,

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

范围的要点是从公共用户界面隐藏原始迭代器。很多时候在迭代时,我们需要 2 个指针;我们容器的开始和结束。范围试图通过隐藏这个接口来简化这一点,并为在 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++ 编写的代码相比,尤其是“视图管道”更易于阅读且更易于维护。