为什么 range::basic_istream_view::begin() 不被缓存?

康桓瑋*_*康桓瑋 6 c++ range-v3 c++20 std-ranges

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

最重要的区别是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
Run Code Online (Sandbox Code Playgroud)

考虑以下内容(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
Run Code Online (Sandbox Code Playgroud)

为什么该标准选择与 range-v3 不同的设计?如果缓存的话是否有潜在的缺陷begin()

Arm*_*yan 5

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

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


Bar*_*rry 5

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

\n
template<class T>\n  concept range =\n    requires(T& t) {\n      ranges::begin(t);   // sometimes equality-preserving (see below)\n      ranges::end(t);\n    };\n
Run Code Online (Sandbox Code Playgroud)\n

其中“见下文”部分是(强调我的):

\n
\n

给定一个表达式,例如t,模型范围仅当decltype((t))T&T

\n
    \n
  • ...
  • \n
  • ranges\xe2\x80\x8b::\xe2\x80\x8bbegin(t)如果模型的类型forward_\xc2\xaditerator,ranges\xe2\x80\x8b::\xe2\x80\x8bbegin(t)是保持相等的。
  • \n
\n

[注 1ranges\xe2\x80\x8b::\xe2\x80\x8bbegin :和的相等保留ranges\xe2\x80\x8b::\xe2\x80\x8bend允许将其迭代器类型模型的范围传递给多个算法,并通过重复调用和forward_\xc2\xaditerator对该范围进行多次传递。由于当返回类型不建模时不需要保持相等,因此重复调用可能不返回相等的值或未明确定义。\xe2\x80\x94 尾注]ranges\xe2\x80\x8b::\xe2\x80\x8bbeginranges\xe2\x80\x8b::\xe2\x80\x8bendranges\xe2\x80\x8b::\xe2\x80\x8bbeginforward_\xc2\xaditerator

\n
\n

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

\n

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

\n

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

\n
\n

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

\n

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

\n
    \n
  • range-v3中,这发生在 的构造函数中istream_view
  • \n
  • libstdc++中,这发生在对begin().
  • \n
\n

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

\n