Почему 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(); });
Ответы
Ответ 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.