有什么方法可以组合 std::istream_iterator 和 std::for_each_n() 吗?

Any way to combine std::istream_iterator and std::for_each_n()?

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

#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:

The actual read operation is performed when the iterator is incremented, not when it is dereferenced. The first object is read when the iterator is constructed. Dereferencing only returns a copy of the most recently read object.

如果我想把std::istream_iteratorstd::for_each_n()一起使用,我猜我运行变成了std::for_each_n()的实现问题,可能实现了(我知道这只是一个例子)根据 cppreference like

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() 的组合是否有效?

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

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 > 0 之前执行 ++__first。因此额外的增量操作。

这是 copy_n 的 libc++ 实现:(这是 non-random-access 迭代器的版本)

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

这里,勾选了__n > 0后,执行++__first。这解释了您观察到的行为。当然,不同的实现可能会有不同的表现。

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

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

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

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

时才可阅读