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

Почему std:: copy_n не увеличивает итератор ввода n раз?

Я ожидаю, что следующее сообщение о buf_iter указывает на символ n после точки начала. Вместо этого он указывает на последний прочитанный символ. Почему это? т.е. если я делаю in_stream.tellg() до и после copy_n, они отличаются не на n, а на (n-1). Если бы я прочитал символы n с in_stream.read, тогда позиция будет продвигаться на n.

std::istreambuf_iterator<char> buf_iter(in_stream);
std::copy_n(buf_iter, n, sym.begin());

Я посмотрел на реализацию, и это явно делает это специально, пропуская окончательный приращение.

Другой пост здесь упоминает, что приращение от итератора при его подключении, скажем, к cin, приведет к слишком большому количеству чтений с момента чтения на operator++(). Это звучит как проблема с cin - почему не прочитано на operator*()?

Указывает ли стандарт в любом месте? Документы, которые я видел, не упоминают, что происходит с итератором, и я видел две разные страницы, которые дают "возможные правильные реализации", которые выполняют каждое из действий:

В cppreference мы имеем:

template< class InputIt, class Size, class OutputIt>
OutputIt copy_n(InputIt first, Size count, OutputIt result)
{
    if (count > 0) {
        *result++ = *first;
        for (Size i = 1; i < count; ++i) {
            *result++ = *++first;
        }
    }
    return result;
}

а на cplusplus.com у нас есть:

template<class InputIterator, class Size, class OutputIterator>
  OutputIterator copy_n (InputIterator first, Size n, OutputIterator result)
{
  while (n>0) {
    *result = *first;
    ++result; ++first;
    --n;
  }
  return result;
}

Оба делают n чтений и приводят к тому же содержимому в результате. Однако первый будет только увеличивать "первый" итератор n-1 раз, а второй увеличивает его n раз.

Что дает? Как написать переносимый код? Я могу использовать tellg и затем seekg, но тогда я мог бы просто выполнить цикл вручную (ugh!).


Обратите внимание, что я не пытаюсь читать из итератора после вызова copy_n, скорее я хочу читать из базового потока после вызова copy_n, и проблема в том, что copy_n остается указывать на короткий байт где я ожидал, что это произойдет. Пока я иду с отвратительной, но, по-видимому, переносной:

auto pos = in_stream.tellg();
std::istreambuf_iterator<char> buf_iter(in_stream);
std::copy_n(buf_iter, cl, sym.begin());

in_stream.seekg(pos + cl);

uint64_t foo;
in_stream.read(reinterpret_cast<char *>(&foo), 8);

BTW, в случае его непонятности я пытаюсь избежать копирования данных в буфер, а затем снова в строку sym.


@DaveS: переходя из моей конкретной проблемы, вот простая программа, которая не выводит то, что я ожидаю, из-за того, что входной итератор не увеличивается в последний раз:

#include <algorithm>
#include <string>
#include <iostream>
#include <fstream>

int main(int argc, const char * argv[])
{
    std::ifstream in("numbers.txt");

    std::istreambuf_iterator<char> in_iter(in);
    std::ostreambuf_iterator<char> out_iter(std::cout);

    std::copy_n(in_iter, 3, out_iter);
    std::cout << std::endl;

    std::copy_n(in_iter, 3, out_iter);
    std::cout << std::endl;

    std::copy_n(in_iter, 3, out_iter);
    std::cout << std::endl;

    return 0;
}

Входной файл равен "0123456789\n"

Я получаю:

012
234
456

Из-за побочного эффекта istreambuf_iterator::operator++() это даст другой результат, если бы был реализован copy_n, чтобы увеличить входной итератор n раз.


@aschepler: нужно зафиксировать локальный параметр, но я собираюсь с ним:

 std::generate_n(sym.begin(), cl, [&in_stream](){ return in_stream.get(); });
4b9b3361

Ответ 1

n3797 [algorithmms.general]/12

В описании алгоритмов операторы + и - используются для некоторых категорий итераторов, для которых они не должны быть определены. В этих случаях семантика a+n такая же, как у

X tmp = a;
advance(tmp, n);
return tmp;

а < <24 > совпадает с

return distance(a, b);

[alg.modifying.operations]

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

5 Эффекты: для каждого неотрицательного целого я < n, выполняет *(result + i) = *(first + i).

6 Возвращает: result + n.

7 Сложность: точно n присвоения.


Я не уверен, что это правильно сформировано для InputIterators (без multipass), поскольку оно не изменяет исходный итератор, но всегда продвигает копию исходного итератора. Это тоже неэффективно.

[input.iterators]/Таблица 107 - Требования ввода итератора (в дополнение к Iterator)

Выражение: ++r
Тип возврата: X&
pre: r является разыменованным.
post: r является разыменованным или r является последним.
post: любые копии предыдущего значения r больше не требуются, чтобы быть разыменованный или находящийся в домене ==.

Насколько я вижу, a in

X tmp = a;
advance(tmp, n);
return tmp;

следовательно, больше не требуется увеличивать.


Связанный отчет о дефекте: LWG 2173

Ответ 2

Причина, по которой многие реализации std::copy_n увеличиваются в n-1 раз за счет взаимодействия с istream_iterator и как это обычно реализуется.

Например, если у вас есть входной файл с целыми числами в них

std::vector<int> buffer(2);
std::istream_iterator<int> itr(stream); // Assume that stream is an ifstream of the file
std::copy_n(itr, 2, buffer.begin());

Поскольку istream_iterator указывается для чтения при инкрементах (и при построении или первом разыменовании), если std::copy_n увеличил входной итератор 2 раза, вы фактически прочитали бы 3 значения из файла. Третье значение просто будет отброшено, если локальный итератор внутри copy_n вышел из области видимости.

istreambuf_iterator не имеет одинаковых взаимодействий, поскольку он фактически не копирует значение из потока в локальную копию, как и большинство istream_iterators do, но copy_n все еще ведет себя таким образом.

Изменить: пример потери данных, если копия N увеличилась в N раз (описание cplusplus.com, которое не кажется правильным). Обратите внимание: это действительно относится только к istream_iterators или другим итераторам, которые читают и удаляют свои базовые данные при приращении.

std::istream_iterator<int> itr(stream); // Reads 1st value

while(n > 0) // N = 2 loop start 
{       
 *result = *first;
 ++result; ++first; // Reads 2nd value
 --n; // N: 1
 // N = 1 loop start
 *result = *first;
 ++result; ++first; // Reads 3rd value
 --n; // N :0
 // Loop exit
}
return result;

Ответ 3

Итеритер источника не берется ссылкой. Таким образом, его копия увеличивается в n раз, но параметр остается нетронутым.

9 из 10 раз, это то, что вы хотите.

Что касается побочных эффектов приращения, специфичных для InputIterators, я думаю, что официально, входные итераторы должны "увеличиваться" на каждом считывании (повторное чтение без приращения не дает одно и то же значение), Итак, просто сделайте приращение no-op.