是否可以将std :: istream_iterator和std :: for_each_n()组合在一起?

时间:2019-06-22 08:42:30

标签: c++ algorithm iterator inputstream

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

#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和输入迭代器first的强度。在我的演示示例中,这导致对std::cin的额外读取。因此,它永远无法用于我的测试程序。 std::copy_n()的实现方式似乎有所不同。当我想将n中的std::cin值读入vector时,以下方法起作用。像

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

所以,我想知道为什么会有不同的行为? std::istream_iteratorstd::for_each_n()的组合能否正常工作?

使用<algorithm>库的替代解决方案是什么?

1 个答案:

答案 0 :(得分:2)

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;
}

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

这是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 > 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的情况下才能阅读。