std :: istream_iterator <> с copy_n () и друзьями - PullRequest
26 голосов
/ 22 февраля 2011

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

Ответы [ 3 ]

10 голосов
/ 27 февраля 2011

К сожалению, разработчик copy_n не смог учесть опережающее чтение в цикле копирования. Реализация Visual C ++ работает, как вы ожидаете, как для 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
*/
4 голосов
/ 13 марта 2013

Сегодня я столкнулся с очень похожей проблемой, и вот пример:

#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

Стандарт c ++ 11 говорит об этом copy_n:

template<class InputIterator, class Size, class OutputIterator>
OutputIterator copy_n(InputIterator first, Size n, OutputIterator result);

Эффекты: для каждого неотрицательного целого числа i Возвращает: результат + n.
Сложность: ровно n заданий.

Как видите, не указано, что именно происходит с итераторами, что означает, что он зависит от реализации.

Мое мнение таково, что в вашем примере не следует читать 3-е значение, а это означает, что в стандарте есть небольшой недостаток: в нем не указано поведение.

1 голос
/ 22 февраля 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...