【问题标题】:Why is ranges::basic_istream_view::begin() not cached?为什么 range::basic_istream_view::begin() 没有被缓存?
【发布时间】:2023-03-20 05:19:01
【问题描述】:

我发现c++20 ranges::basic_istream_viewrange-v3版本略有不同。

最重要的区别是std::ranges::basic_istream_view不缓存它的begin(),这样每个begin()s都会返回下一个迭代器,它的值已经被读取(godbolt):

auto words = std::istringstream{"today is yesterday's tomorrow"};
auto view = std::ranges::istream_view<std::string>(words);
std::cout << *view.begin() << "\n"; // today
std::cout << *view.begin() << "\n"; // is
std::cout << *view.begin() << "\n"; // yesterday's
std::cout << *view.begin() << "\n"; // tomorrow

考虑以下(godbolt),如果我使用 range-v3 版本,所有三个std::ranges::find()s 都会找到"is",但如果我使用 std 版本,"is" 只会在第一次通话。

auto words = std::istringstream{"today is yesterday's tomorrow"};
auto view = std::ranges::istream_view<std::string>(words);
std::cout << *std::ranges::find(view, "is") << "\n"; // is
std::cout << *std::ranges::find(view, "is") << "\n"; // tomorrow
std::cout << *std::ranges::find(view, "is") << "\n"; // tomorrow

为什么标准选择了与 range-v3 不同的设计?如果begin()被缓存,会不会有潜在的缺陷?

【问题讨论】:

    标签: c++ c++20 range-v3 std-ranges


    【解决方案1】:

    range 概念的定义中,在[range.range] 中,我们有:

    template<class T>
      concept range =
        requires(T& t) {
          ranges::begin(t);   // sometimes equality-preserving (see below)
          ranges::end(t);
        };
    

    “见下文”部分在哪里(强调我的):

    给定一个表达式t 使得decltype((t))T&amp;T 模型范围仅当

    • ...
    • 如果ranges​::​begin(t) 模型forward_­iteratorranges​::​begin(t) 的类型是保持平等的。

    [注 1ranges​::​beginranges​::​end 的平等保留允许将其迭代器类型模型 forward_­iterator 的范围传递给多个算法,并通过重复调用对范围进行多次传递ranges​::​beginranges​::​end由于在返回类型与 forward_­iterator 不同时,ranges​::​begin 不需要保持相等,因此重复调用可能不会返回相等的值或定义不明确。 —尾注]

    对于前向范围,您可以反复调用ranges::begin(r) 并期望得到相同的答案(它是保持平等的)。对于某些范围,这需要缓存。

    但对于仅输入范围(如istream_view),您可以只调用一次ranges::begin(r),并且不能保证第二次调用会发生什么。

    因此,有效程序无法观察到两种实现之间的差异,因为通过多次调用begin,您已经违反了这里的先决条件。


    对于istream_view 的更具体的答案,range-v3 的实现也不会缓存begin()。这不是正在发生的差异。事实上,无论如何你都不能缓存输入迭代器,因为它们会立即失效。

    区别在于我们从流中读取第一个值:

    • range-v3 中,发生在istream_view 的构造函数中。
    • libstdc++ 中,这发生在对begin() 的调用中。

    后者更符合一般的 Ranges 模型,其中构建范围适配器实际上并不做任何工作。

    【讨论】:

      【解决方案2】:

      输入迭代器是这样的,一旦你取消引用一个,你需要立即增加它。输出迭代器也是如此,例如 back_insert_iterator。这是你不应该做的事情。如果您需要缓存第一个值,请自行缓存。

      输入和输出迭代器在解除引用后需要递增的原因是它们设计为单通道。如果您从流中读取了某些内容,则无法再次读取它。运算符 * 实际上是从流中读取的。 ++ 做什么?没有! back_insert_iterator 也是如此

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-17
        • 2011-05-17
        • 2017-02-20
        • 1970-01-01
        • 1970-01-01
        • 2020-07-31
        相关资源
        最近更新 更多