Std:: istream_iterator <> с copy_n() и друзьями
В приведенном ниже фрагменте читаются три целых числа из std::cin
; он записывает два в numbers
и отбрасывает третью:
std::vector<int> numbers(2);
copy_n(std::istream_iterator<int>(std::cin), 2, numbers.begin());
Я бы ожидал, что код будет читать ровно два целых числа от std::cin
, но оказывается, что это правильное, стандартно-совместимое поведение. Является ли это надзором в стандарте? В чем причина такого поведения?
От 24.5.1/1 в стандарте С++ 03:
После того, как он будет построен, и каждый используется время ++, итератор читает и сохраняет значение T
.
Итак, в приведенном выше коде в точке вызова итератор потока уже считывает одно целое число. С этого момента каждый, прочитанный итератором в алгоритме, является прочитанным вперед, что дает значение, кэшированное из предыдущего чтения.
Последний черновик следующего стандарта n3225, похоже, не имеет никаких изменений здесь (24.6.1/1).
В соответствующей заметке 24.5.1.1/2 текущего стандарта в отношении конструктора istream_iterator(istream_type& s)
читает
Эффекты: Инициализирует in_stream
с помощью s
. value
может быть инициализирован во время строительства или в первый раз ссылка.
С акцентом на "value
может быть инициализировано...", в отличие от " должен быть инициализирован". Это звучит противоречащим 24.5.1/1, но, возможно, это заслуживает отдельного вопроса.
Ответы
Ответ 1
К сожалению, разработчик copy_n не смог учесть чтение в цикле копирования. Реализация Visual С++ работает так, как вы ожидаете, и для stringstream, и для std:: cin. Я также проверил случай из исходного примера, где istream_iterator сконструирован в строке.
Вот ключевая часть кода из реализации STL.
template<class _InIt,
class _Diff,
class _OutIt> inline
_OutIt _Copy_n(_InIt _First, _Diff _Count,
_OutIt _Dest, input_iterator_tag)
{ // copy [_First, _First + _Count) to [_Dest, ...), arbitrary input
*_Dest = *_First; // 0 < _Count has been guaranteed
while (0 < --_Count)
*++_Dest = *++_First;
return (++_Dest);
}
Вот тестовый код
#include <iostream>
#include <istream>
#include <sstream>
#include <vector>
#include <iterator>
int _tmain(int argc, _TCHAR* argv[])
{
std::stringstream ss;
ss << 1 << ' ' << 2 << ' ' << 3 << ' ' << 4 << std::endl;
ss.seekg(0);
std::vector<int> numbers(2);
std::istream_iterator<int> ii(ss);
std::cout << *ii << std::endl; // shows that read ahead happened.
std::copy_n(ii, 2, numbers.begin());
int i = 0;
ss >> i;
std::cout << numbers[0] << ' ' << numbers[1] << ' ' << i << std::endl;
std::istream_iterator<int> ii2(std::cin);
std::cout << *ii2 << std::endl; // shows that read ahead happened.
std::copy_n(ii2, 2, numbers.begin());
std::cin >> i;
std::cout << numbers[0] << ' ' << numbers[1] << ' ' << i << std::endl;
return 0;
}
/* Output
1
1 2 3
4 5 6
4
4 5 6
*/
Ответ 2
Сегодня я столкнулся с очень похожей проблемой, и вот пример:
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <string>
struct A
{
float a[3];
unsigned short int b[6];
};
void ParseLine( const std::string & line, A & a )
{
std::stringstream ss( line );
std::copy_n( std::istream_iterator<float>( ss ), 3, a.a );
std::copy_n( std::istream_iterator<unsigned short int>( ss ), 6, a.b );
}
void PrintValues( const A & a )
{
for ( int i =0;i<3;++i)
{
std::cout<<a.a[i]<<std::endl;
}
for ( int i =0;i<6;++i)
{
std::cout<<a.b[i]<<std::endl;
}
}
int main()
{
A a;
const std::string line( "1.1 2.2 3.3 8 7 6 3 2 1" );
ParseLine( line, a );
PrintValues( a );
}
Компиляция приведенного выше примера с помощью g++ 4.6.3 дает один:
1.1 2.2 3.3 7 6 3 2 1 1
а компиляция с g++ 4.7.2 дает другой результат:
1.1 2.2 3.3 8 7 6 3 2 1
В стандарте С++ 11 говорится об copy_n
:
template<class InputIterator, class Size, class OutputIterator>
OutputIterator copy_n(InputIterator first, Size n, OutputIterator result);
Эффекты: для каждого неотрицательного целого я < n, выполняет * (результат + i) = * (первый + i).
Возвраты: результат + n.
Сложность: точно n заданий.
Как вы можете видеть, не указано, что именно происходит с итераторами, что означает, что он зависит от реализации.
Мое мнение таково, что ваш пример не должен читать третье значение, а это означает, что это небольшой недостаток в стандарте, который они не указали.
Ответ 3
Я не знаю точного обоснования, но поскольку итератору также необходимо поддерживать оператор *(), он должен будет кэшировать значения, которые он читает. Разрешить итератору кэшировать первое значение при построении упрощает это. Это также помогает в обнаружении конца потока, когда поток изначально пуст.
Возможно, ваш вариант использования - это тот, который комитет не рассматривал?