有什么方法可以组合 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_iterator
和std::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_iterator
和 std::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_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;
}
请注意,它会在检查 --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])。也许你可以持有一个初始化为 true
的 can_read
成员,当迭代器被取消引用时设置为 false
,当迭代器递增时设置为 true
,所以仅当 can_read
为 true
.
时才可阅读
我创建了以下纯测试程序来说明问题:
#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_iterator
和std::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_iterator
和 std::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_n
和 for_each_n
没有设计迭代器,迭代器在递增时会产生副作用。换句话说,istream_iterator
不是为这种情况设计的。
标准没有指定在这种情况下应该发生什么。我们来看看for_each_n
:
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])。也许你可以持有一个初始化为 true
的 can_read
成员,当迭代器被取消引用时设置为 false
,当迭代器递增时设置为 true
,所以仅当 can_read
为 true
.