【问题标题】:Why can't I use istream_view and std::accumulate to sum up my input?为什么我不能使用 istream_view 和 std::accumulate 来总结我的输入?
【发布时间】:2021-03-26 11:49:21
【问题描述】:

这个程序:

#include <ranges>
#include <numeric>
#include <iostream>

int main() {
    auto rng = std::ranges::istream_view<int>(std::cin);
    std::cout << std::accumulate(std::ranges::begin(rng), std::ranges::end(rng), 0);
}

应该总结所有在标准输入流中显示为文本的整数。但是 - 它不能编译。我知道std::ranges::begin()std::ranges::end() 存在,那么这是怎么回事?编译器告诉我找不到合适的候选人;为什么?

【问题讨论】:

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


    【解决方案1】:

    从开始到 C++17,&lt;algorithm&gt; 中的所有内容都基于迭代器对:您有一个 iterator 指代范围的开头,一个 iterator 指代范围的结尾,始终具有相同的类型。

    在 C++20 中,这是通用的。范围现在由 iterator 和该迭代器的 sentinel 表示 - 其中 sentinel 本身实际上不需要是任何类型的迭代器,它只需要是可以比较等于其相应迭代器的类型(这是sentinel_for 的概念)。

    C++17 范围往往是 有效的 C++20 范围,但不一定是相反的方向。一个原因是能够拥有一个独特的 sentinel 类型,但还有其他的,这也涉及到这个问题(见下文)。

    为了配合新模型,C++20 在std::ranges 命名空间中添加了大量算法,这些算法采用iteratorsentinel,而不是两个iterators。例如,虽然我们一直有:

    template<class InputIterator, class T>
      constexpr InputIterator find(InputIterator first, InputIterator last,
                                   const T& value);
    

    我们现在也有:

    namespace ranges {
      template<input_­iterator I, sentinel_­for<I> S, class T, class Proj = identity>
        requires indirect_­binary_­predicate<ranges::equal_to, projected<I, Proj>, const T*>
        constexpr I find(I first, S last, const T& value, Proj proj = {});
    
      template<input_­range R, class T, class Proj = identity>
        requires indirect_­binary_­predicate<ranges::equal_to,
                                           projected<iterator_t<R>, Proj>, const T*>
        constexpr borrowed_iterator_t<R>
          find(R&& r, const T& value, Proj proj = {});
    }
    

    这里的第一个重载采用 iterator/sentinel 对,而第二个重载采用范围。


    虽然许多算法在std::ranges 中添加了相应的重载,但&lt;numeric&gt; 中的重载被忽略了。有std::accumulate,但没有std::ranges::accumulate。因此,我们目前唯一可用的版本是采用迭代器对的版本。否则,你可以写:

    auto rng = std::ranges::istream_view<int>(std::cin);
    std::cout << std::ranges::accumulate(rng, 0);
    

    不幸的是,std::ranges::istream_view 是新的 C++20 范围之一,其标记类型与其迭代器类型不同,因此您也不能将 rng.begin()rng.end() 传递给 std::accumulate

    这通常给您留下两个选择(三个,如果您包括等待 C++23,希望有一个 std::ranges::fold):

    1. 编写您自己的基于范围和基于迭代器哨兵的算法。对于fold,这很容易做到。

    或者

    1. 有一个实用程序可以将 C++20 范围包装成与 C++17 兼容的范围:views::common。所以你可以这样做:
    auto rng = std::ranges::istream_view<int>(ints) | std::views::common;
    std::cout << std::accumulate(rng.begin(), rng.end(), 0);
    

    在这种特殊情况下除外。

    istream_view 的迭代器是不可复制的,在 C++17 中所有的迭代器都必须是。所以实际上没有办法提供基于istream_view 的兼容 C++17 的迭代器。您需要适当的 C++20 范围支持。未来的std::ranges::fold 将支持只移动视图和只移动迭代器,但std::accumulate 永远不能。

    在这种情况下,只留下选项 1。


    C++20 迭代器必须是可默认构造的,这不是 C++17 迭代器的要求。因此,具有不可默认构造的迭代器的 C++17 范围将不是有效的 C++20 范围。

    【讨论】:

    • "在这种情况下,只保留选项 1" - 或选项 3:根本不使用 std::ranges::istream_view,而是使用 std::istream_iterator,例如:@987654359 @
    • @RemyLebeau Meh。我还是更喜欢 1。它甚至不是十几行代码,而且用途广泛。另外,如果你想对输入中的偶数求和怎么办?还是输入的平方?或者...
    • “不幸的是,...与其迭代器类型不同”
    • @einpoklum 鉴于它阻止你做你想做的事,我会把它归类为不幸的,是的。如果你不这样做,那很好,但不要到处编辑别人的答案,因为你对单词选择有不同的偏好——这不是编辑的目的。
    • @Barry:但它本身并不会阻止我做我想做的事。我想,如果我有选择的话,我不会把哨兵变成一个合适的迭代器。
    【解决方案2】:

    问题在于,C++ 范围的结尾不是,在一般情况下,它是一个迭代器,而是一个哨兵​​m>。哨兵可以具有与迭代器不同的类型并允许更少的操作 - 因为,一般来说,您最需要与它进行比较才能知道您已经到达范围的末尾,并且可能不允许像使用它一样使用它任何迭代器。有关此区别的更多信息,请阅读:

    What's the difference between a sentinel and an end iterator?

    现在,标准库算法(包括 &lt;numeric&gt; 中的算法)采用相同类型的迭代器对。在您的示例中:

    template< class InputIt, class T >
    constexpr T accumulate( InputIt first, InputIt last, T init );
    

    看到了吗? InputIt 必须是范围开头和结尾的类型。对于istream_view 范围,这甚至可能(可能)无法“修复”,因为标准输入的结尾本身并不是迭代器。 (虽然也许你可以将它变成一个迭代器并在用它做不相关的事情时抛出异常。)

    因此,我们需要std::accumulate 的新变体或std::ranges::accumulate,我们currently don't have。或者,当然,你也可以自己写,应该不会太难。

    编辑:@RemyLebeau 建议的最后一个选项是改用std::istream_iterator

    std::cout << std::accumulate(
        std::istream_iterator<int>(std::cin), 
        std::istream_iterator<int>(), 
        0);
    

    【讨论】:

      猜你喜欢
      • 2021-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多