【问题标题】:Any way to combine std::istream_iterator and std::for_each_n()?有什么方法可以结合 std::istream_iterator 和 std::for_each_n()?
【发布时间】:2019-11-04 22:14:35
【问题描述】:

我创建了以下纯测试程序来说明问题:

#include <iostream>
#include <iterator>
#include <algorithm>

int main()  // Test program. Not for real life solution. Just for demonstrating the question
{
    int odd{ 0 }, even{ 0 }, nValues{ 0 };

    std::cout << "Check and count odd and even numbers\n\nHow many numbers to check? Enter a value:  ";
    std::cin >> nValues;

    std::for_each_n(std::istream_iterator<int>(std::cin), nValues, [&](const int i) {if ((i % 2) == 0) ++even; else ++odd; });

    std::cout << "\nYou entered '" << even << "' even values and '" << odd << "' odd values\n\n";
    return 0;
}

如果我输入n,则将读取n+1 值。

cppreference 解释了istream_iterator

实际的读取操作是在迭代器递增时执行的,而不是在它被取消引用时执行的。当迭代器构造时,第一个对象被读取。取消引用仅返回最近读取的对象的副本。

如果我想将std::istream_iteratorstd::for_each_n()一起使用,我想我遇到了std::for_each_n()的实现问题,可能根据cppreference实现(我知道这只是一个例子)喜欢

template<class InputIt, class Size, class UnaryFunction>
InputIt for_each_n(InputIt first, Size n, UnaryFunction f)
{
    for (Size i = 0; i < n; ++first, (void) ++i) {
        f(*first);
    }
    return first;
}

因此,为了检查f() 是否已被调用n 次,它递增i AND 输入迭代器first。在我的演示示例中,这会导致对std::cin 的额外阅读。所以,它永远无法为我的测试程序工作。 std::copy_n() 的实现方式似乎有所不同。当我想将std::cin 中的n 值读入vector 时,以下工作。喜欢

std::vector<int> v(3);std::copy_n(std::istream_iterator<int>(std::cin), 3, v.begin());

那么,我想知道为什么会有不同的行为? std::istream_iteratorstd::for_each_n() 的组合可以工作吗?

使用&lt;algorithm&gt; 库的替代解决方案是什么?

【问题讨论】:

    标签: c++ algorithm iterator inputstream


    【解决方案1】:

    istream_iterator 最初是为[begin, end) 范围设计的,如

    std::for_each(std::istream_iterator<int>{std::cin}, std::istream_iterator<int>{}, /* do something */);
    

    给定n 值,代码首先读取这些n 值,然后尝试读取一次(!)并遇到EOF,导致循环终止。在您的情况下,此尝试会导致额外读取。 copy_nfor_each_n 未设计有迭代器,这些迭代器在递增时会导致副作用。换句话说,istream_iterator 不是为这种情况设计的。

    标准没有规定在这种情况下应该发生什么。我们来看看for_each_n的libc++实现:

    template <class _InputIterator, class _Size, class _Function>
    inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
    _InputIterator
    for_each_n(_InputIterator __first, _Size __orig_n, _Function __f)
    {
        typedef decltype(__convert_to_integral(__orig_n)) _IntegralSize;
        _IntegralSize __n = __orig_n;
        while (__n > 0)
        {
             __f(*__first);
             ++__first;
             --__n;
        }
        return __first;
    }
    

    请注意,在检查--n, __n &gt; 0 之前,它会执行++__first。因此需要额外的增量操作。

    这是 copy_n 的 libc++ 实现:(这是非随机访问迭代器的版本)

    template<class _InputIterator, class _Size, class _OutputIterator>
    inline _LIBCPP_INLINE_VISIBILITY
    typename enable_if
    <
        __is_input_iterator<_InputIterator>::value &&
       !__is_random_access_iterator<_InputIterator>::value,
        _OutputIterator
    >::type
    copy_n(_InputIterator __first, _Size __orig_n, _OutputIterator __result)
    {
        typedef decltype(__convert_to_integral(__orig_n)) _IntegralSize;
        _IntegralSize __n = __orig_n;
        if (__n > 0)
        {
            *__result = *__first;
            ++__result;
            for (--__n; __n > 0; --__n)
            {
                ++__first;
                *__result = *__first;
                ++__result;
            }
        }
        return __result;
    }
    

    这里,++__first 在检查__n &gt; 0 之后执行。这解释了您观察到的行为。当然,不同的实现可能会有不同的表现。

    解决此问题的最简单方法是编写手动循环:

    for (int i = 0; i < nValues; ++i) {
        int x;
        std::cin >> x;
        do_something(x);
    }
    

    我不会说这比使用标准算法差。

    当然,您也可以编写自己的 istream 迭代器,该迭代器在取消引用时读取,而不是递增,但是您必须确保连续的取消引用操作不会导致多次读取操作 ([tab:inputiterator])。也许您可以持有一个can_read 成员,该成员初始化为true,在取消引用迭代器时设置为false,并在迭代器递增时设置为true,因此只有当can_readtrue.

    【讨论】:

    • 感谢您分享详细信息。这证实了我对实施的假设。嗯,那么问题是为什么会有这种不同的实现方式?而且,结果是 std::istream_iterator 和 std::for_each_n 永远不能以有意义的方式组合吗?
    • @ArminMontigny 为什么会有如此不同的实现方式?没有特别的原因。 那么结果是,std::istream_iterator 和 std::for_each_n 永远不能以有意义的方式组合吗? 也许可以,但可能不是这样。
    • 严重滥用 std::find_if 并使用副作用(啊!),我们可以写:“std::find_if(std::istream_iterator(std::cin), std ::istream_iterator(), [&](const int i) {if ((i % 2) == 0) ++even; else ++odd; return (--nValues == 0); }) ;"。我保证我永远不会使用这个解决方案。 :-)
    • @ArminMontigny find_if 是否将迭代器递增到找到的元素也是未指定的 AFAIK。
    猜你喜欢
    • 2019-04-01
    • 2017-03-13
    • 2011-07-01
    • 2016-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-27
    • 2011-04-06
    相关资源
    最近更新 更多