我创建了以下纯测试程序来说明该问题:
#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_iterator
与std::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_iterator
和std::for_each_n()
的组合能否正常工作?
使用<algorithm>
库的替代解决方案是什么?
答案 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_n
和for_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_read
为true
的情况下才能阅读。