Подтвердить что ты не робот

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, но, возможно, это заслуживает отдельного вопроса.

4b9b3361

Ответ 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

Я не знаю точного обоснования, но поскольку итератору также необходимо поддерживать оператор *(), он должен будет кэшировать значения, которые он читает. Разрешить итератору кэшировать первое значение при построении упрощает это. Это также помогает в обнаружении конца потока, когда поток изначально пуст.

Возможно, ваш вариант использования - это тот, который комитет не рассматривал?