Ограничение диапазона для std::copy с помощью std::istream_iterator

Я создал минимальный рабочий пример, чтобы показать проблему, с которой я столкнулся при использовании итераторов STL. Я использую istream_iterator для чтения floatss (или других типов) из std::istream:

#include <iostream>
#include <iterator>
#include <algorithm>

int main() {
   float values[4];
   std::copy(std::istream_iterator<float>(std::cin), std::istream_iterator<float>(), values);
   std::cout << "Read exactly 4 floats" << std::endl; // Not true!
}

Это читает все возможные floatss, до EOF в values, который имеет фиксированный размер, 4, поэтому теперь ясно, что я хочу ограничить диапазон, чтобы избежать переполнения и прочитать точно/не более 4 значений.

С более «обычными» итераторами (например, RandomAccessIterator), при условии, что begin+4 не превышает конца, который вы бы сделали:

std::copy(begin, begin+4, out);

Чтобы прочитать ровно 4 элемента.

Как это сделать с std::istream_iterator? Очевидная идея состоит в том, чтобы изменить вызов std::copy на:

std::copy(std::istream_iterator<float>(std::cin), std::istream_iterator<float>(std::cin)+4, values);

Но (довольно предсказуемо) это не компилируется, кандидатов на operator+ нет:

g++ -Wall -Wextra test.cc
test.cc: In function ‘int main()’:
test.cc:7: error: no match for ‘operator+’ in ‘std::istream_iterator<float, char, std::char_traits<char>, long int>(((std::basic_istream<char, std::char_traits<char> >&)(& std::cin))) + 4’

Какие-либо предложения? Есть ли правильный способ "STLified" pre-C++0x для достижения этого? Очевидно, что я мог просто написать это как цикл for, но я хочу узнать кое-что о STL здесь. Я наполовину задавался вопросом о злоупотреблении std::transform или std::merge и т. д., чтобы каким-то образом добиться этой функциональности, но я не совсем понимаю, как это сделать.


person Flexo    schedule 10.05.2011    source источник
comment
+1 за краткий самодостаточный полный пример (см. sscce.org).   -  person Robᵩ    schedule 10.05.2011
comment
@Rob - я всегда использовал термин MWE для минимального рабочего примера, но это довольно хороший способ описать его, особенно с полезным текстом по этому URL-адресу.   -  person Flexo    schedule 10.05.2011
comment
Связано: stackoverflow.com/questions/3829885   -  person Björn Pollex    schedule 10.05.2011


Ответы (3)


Поскольку вы запросили решение, отличное от C++0x, вот альтернатива, использующая std::generate_n и функтор-генератор, а не std::copy_n и итераторы:

#include <algorithm>
#include <string>
#include <istream>
#include <ostream>
#include <iostream>

template<
    typename ResultT,
    typename CharT = char,
    typename CharTraitsT = std::char_traits<CharT>
>
struct input_generator
{
    typedef ResultT result_type;

    explicit input_generator(std::basic_istream<CharT, CharTraitsT>& input)
      : input_(&input)
    { }

    ResultT operator ()() const
    {
        // value-initialize so primitives like float
        // have a defined value if extraction fails
        ResultT v((ResultT()));
        *input_ >> v;
        return v;
    }

private:
    std::basic_istream<CharT, CharTraitsT>* input_;
};

template<typename ResultT, typename CharT, typename CharTraitsT>
inline input_generator<ResultT, CharT, CharTraitsT> make_input_generator(
    std::basic_istream<CharT, CharTraitsT>& input
)
{
    return input_generator<ResultT, CharT, CharTraitsT>(input);
}

int main()
{
    float values[4];
    std::generate_n(values, 4, make_input_generator<float>(std::cin));
    std::cout << "Read exactly 4 floats" << std::endl;
}

При желании вы можете использовать этот генератор вместе с boost::generator_iterator для использования генератора в качестве итератора ввода.

person ildjarn    schedule 10.05.2011
comment
Интересно, что generate_n гарантирует, что выполняется только n вызов operator(), и, таким образом, у вас нет проблемы со счетом, которая может возникнуть при copy_n. Кроме того, если специализировано для std::cin, можно избежать предиката и использовать простую функцию (template <typename T> T get() { T t = T(); std::cin >> t; return t; }). Кроме того, он позволяет легко проверить состояние входа (std::cin). - person Matthieu M.; 10.05.2011
comment
Я принял этот ответ на основании того, что он не C++0x-ness, в сочетании с тем, чтобы избежать проблем, обсуждаемых с copy_n и итераторами ввода. - person Flexo; 11.05.2011

Взгляните на std::copy_n.

person Igor Nazarenko    schedule 10.05.2011
comment
@Igor Nazarenko: Справедливо, но вы должны отметить, что этот алгоритм является новым для C++0x и недоступен в стандартных библиотеках старых компиляторов. - person ildjarn; 10.05.2011
comment
Ах, эта часть C++0x может быть проблемой для меня здесь, и это объясняет, почему я не заметил ее в своей книге STL эпохи C++98. Любое решение, отличное от С++ 0x? Я посмотрел, есть ли хитрый способ (ab) использовать что-то вроде std::transform или std::swap_range для достижения этого, но я не увидел ничего очевидного. - person Flexo; 10.05.2011
comment
Я не знал, что это не в C++98 :-) Это в GCC с ~ 2008 года, я думаю, я избалован. С какими компиляторами вам нужно быть совместимым? - person Igor Nazarenko; 10.05.2011
comment
@Igor Я лично использую gcc 4.4, но до сих пор я избегал C++0x в этом проекте, так как у меня есть сильное подозрение, что кто-то захочет скомпилировать его со старым (выше) VS (хотя и новее 6) в ближайшем будущем. будущее, и у меня сложилось впечатление, что если я это сделаю, это потребует боли. - person Flexo; 10.05.2011
comment
Использование copy_n имеет свою проблему с количеством раз, когда он увеличивает istream_iterator http://stackoverflow.com/questions/5074122/stdistream-iterator-with-copy-n-and-friends В этом случае он может скопировать 4 числа с плавающей запятой, но может также прочитать 5 из cin ! - person Bo Persson; 10.05.2011
comment
Он присутствует только с VS 2010. - person Igor Nazarenko; 10.05.2011
comment
также немного любопытно, как вы должны проверять успех с помощью copy_n и istream_iterators - тестирования итератора, возвращаемого из copy_n, с помощью std::istream_iterator‹float›() (т.е. end) недостаточно, не так ли? И значения после любого сбоя предположительно будут неинициализированы. - person Flexo; 10.05.2011
comment
@ Бо Перссон - тогда это убийца! Похоже на небольшой недостаток, учитывая, что документация для copy_n подразумевает, что единственный случай, когда он вам когда-либо понадобится, а не просто копирование, - это итераторы ввода вместо итераторов вперед. - person Flexo; 10.05.2011
comment
@awoodland: на самом деле он вам понадобится для всего, что не поддерживает +, а это что угодно, кроме RandomAccessIterator. Однако InputIterator сложна :/ - person Matthieu M.; 10.05.2011
comment
@Matthieu M. Где вы нашли необходимость в operator+? И copy, и copy_n принимают итераторы ввода, а не итераторы произвольного доступа. - person ildjarn; 10.05.2011
comment
@ildjarn - вам нужен operator+, если вы хотите сгенерировать конец для диапазона, состоящего из n элементов, переданных первому итератору, вместо того, чтобы просто вызывать end() для контейнера и т. д. - person Flexo; 10.05.2011
comment
@awoodland: не обязательно; std::advance работает с итераторами ввода. - person ildjarn; 10.05.2011
comment
@ildjarn: из сообщения @awoodland, где он пытался использовать его, чтобы указать правильный диапазон для алгоритма copy. Вы не можете построить правильный диапазон без фактического чтения всего этого диапазона, кроме как с RandomAccessIterator (независимо от того, используете ли вы + или advance). - person Matthieu M.; 10.05.2011
comment
@ildjarn: advance работает с InputIterator, но вы не можете построить с его помощью диапазон. - person Matthieu M.; 10.05.2011

Если у вас нет std::copy_n, довольно легко написать свой собственный:

namespace std_ext { 
template<class InputIterator, class Size, class OutputIterator>
OutputIterator copy_n(InputIterator first, Size n, OutputIterator result) {
    for (int i=0; i<n; i++) {
        *result = *first;
        ++result;
        ++first;
    }
    return result;
}
}
person Jerry Coffin    schedule 10.05.2011
comment
На самом деле, в вашем примере может быть ошибка, вызвавшая обсуждение, связанное с @Bo Person: то есть first увеличивается n раз и, таким образом, считывается n+1 значений. Говард Хиннант опубликовал фиксацию патча для libc++ в одном из комментариев, предоставив реализацию, в которой нет проблемы: lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20110221/ - person Matthieu M.; 10.05.2011
comment
@Matthieu: Возможно, мне придется немного поговорить об этом с Говардом. Мне кажется, что исправленная версия возвращает input+n-1, где требуется вернуть input+n. Это может быть более полезным поведением при некоторых обстоятельствах, но если я не ошибаюсь, это все равно выглядит как нарушение требования. - person Jerry Coffin; 10.05.2011
comment
@Jerry: Есть ли продолжение вашего последнего комментария? - person ildjarn; 26.08.2011
comment
@ildjarn: Да - я неправильно прочитал код; он (правильно) возвращает input+n. - person Jerry Coffin; 27.08.2011