4

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

最重要的区别是sstd::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()如果被缓存,是否有任何潜在的缺陷?

4

2 回答 2

4

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

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

于 2021-05-14T12:53:58.810 回答
4

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&T模型范围仅当

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

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

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

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

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


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

不同的是,我们从流中读取第一个值时:

  • range-v3中,这发生在istream_view.
  • libstdc++中,这发生在对begin().

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

于 2021-05-14T13:50:32.280 回答