27

I have constructed a minimal working example to show a problem I've encountered using STL iterators. I'm using istream_iterator to read floatss (or other types) from a 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!
}

This reads all possible floatss, until EOF into values, which is of fixed size, 4, so now clearly I want to limit the range to avoid overflows and read exactly/at most 4 values.

With more "normal" iterators (i.e. RandomAccessIterator), provided begin+4 isn't past the end you'd do:

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

To read exactly 4 elements.

How does one do this with std::istream_iterator? The obvious idea is to change the call to std::copy to be:

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

But (fairly predictably) this doesn't compile, there are no candidates for 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’

Any suggestions? Is there a correct, "STLified" pre-C++0x way to achieve this? Obviously I could just write it out as a for loop, but I'm looking to learn something about the STL here. I half wondered about abusing std::transform or std::merge etc. to achieve this functionality somehow, but I can't quite see how to do it.

Flexo
  • 87,323
  • 22
  • 191
  • 272

3 Answers3

15

Take a look at std::copy_n 

Igor Nazarenko
  • 2,184
  • 13
  • 10
  • 3
    @Igor Nazarenko : Fair, but you should note that this algorithm is new to C++0x, and not available in older compilers' standard libraries. – ildjarn May 10 '11 at 17:06
  • Ah that C++0x part might be a problem for me here then and would explain why I didn't spot it in my C++98 era STL book. Any Non C++0x solution? I had a look to see if there was a cunning way of (ab)using something like std::transform or std::swap_range to achieve this, but I couldn't see anything obvious. – Flexo May 10 '11 at 17:10
  • I didn't realize it's not in C++98 :-) It's in GCC since ~2008, I guess I'm spoiled. Which compilers do you need to be compatible to? – Igor Nazarenko May 10 '11 at 17:21
  • @Igor I'm using gcc 4.4 personally, but I had avoided C++0x so far in this project as I have a strong suspicion someone will want to compile it with old(ish) VS (newer than 6 though) in the near future and I was under the impression that it would be asking for pain if I did. – Flexo May 10 '11 at 17:26
  • 3
    Use of copy_n has its own problem with the number of times it increments the istream_iterator [http://stackoverflow.com/questions/5074122/stdistream-iterator-with-copy-n-and-friends](http://stackoverflow.com/questions/5074122/stdistream-iterator-with-copy-n-and-friends) In this case it might copy 4 floats, but *might* also read 5 from cin! – Bo Persson May 10 '11 at 17:28
  • It's only present from VS 2010 on. – Igor Nazarenko May 10 '11 at 17:30
  • also on a side note slightly curious as to how you're supposed to check for success with copy_n and istream_iterators - testing the iterator returned from copy_n with std::istream_iterator() (i.e. end) isn't sufficient is it? And the values after any failure would be uninitalised presumably. – Flexo May 10 '11 at 17:34
  • @Bo Persson - that's a killer then! Seems like a bit of a flaw really given that the documentation for copy_n implies the only case you'd ever need it over just copy is for input iterators instead of forward iterators. – Flexo May 10 '11 at 17:39
  • @awoodland: actually you'd would need it for anything that does not support `+`, which is anything apart from RandomAccessIterator. InputIterator are tricky though :/ – Matthieu M. May 10 '11 at 17:42
  • @Matthieu M. : Where are you coming up with the need for `operator+`? Both `copy` and `copy_n` take input iterators, not random access iterators. – ildjarn May 10 '11 at 17:48
  • @ildjarn - you need an `operator+` if you want to generate an end for the range that's n elements passed the first iterator instead of just calling end() on a container etc. – Flexo May 10 '11 at 17:50
  • @awoodland : Not necessarily; [`std::advance`](http://www.cppreference.com/wiki/iterator/start#auxiliary_iterator_functions) works on input iterators. – ildjarn May 10 '11 at 17:54
  • 2
    @ildjarn: from @awoodland post, where he tried to use it so that he could specify a "proper" range for the `copy` algorithm. You cannot build a proper range without actually reading that whole range except with RandomAccessIterator (whether using `+` or `advance`). – Matthieu M. May 10 '11 at 17:55
  • 4
    @ildjarn: `advance` works on InputIterator, but you cannot build a range with it. – Matthieu M. May 10 '11 at 17:57
14

As you requested a non-C++0x solution, here's an alternative that uses std::generate_n and a generator functor rather than std::copy_n and iterators:

#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;
}

If you wanted to, you could then use this generator in conjunction with boost::generator_iterator to use the generator as an input iterator.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • 2
    Interesting, as `generate_n` guarantes that only `n` call to `operator()` are performed, and thus you don't have the count problem that `copy_n` might exhibit. Also, if specialized for `std::cin`, one can avoid the predicate and use a simple function (`template T get() { T t = T(); std::cin >> t; return t; }`). Furthermore, it allows checking the state of the input (`std::cin`) easily. – Matthieu M. May 10 '11 at 18:07
  • I accepted this answer on the grounds of non C++0x-ness, combined with avoiding the problems discussed with copy_n and input iterators. – Flexo May 11 '11 at 08:09
3

If you don't have std::copy_n available, it's pretty easy to write your own:

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;
}
}
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 1
    Actualy, your example might have the bug that originated the discussion @Bo Person linked: ie, `first` being incremented n times, and thus reading n+1 values. Howard Hinnant posted the a commit of the patch for libc++ in one of the comment, giving an implementation that does not have the issue: http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20110221/039404.html – Matthieu M. May 10 '11 at 17:50
  • @Matthieu: I may have to talk a bit with Howard about that. It looks to me like the "corrected" version returns `input+n-1`, where it's required to return `input+n`. That may be more useful behavior under some circumstances, but unless I'm misreading, it still looks like it's violating a requirement. – Jerry Coffin May 10 '11 at 19:07
  • @Jerry : Is there any followup to your last comment? – ildjarn Aug 26 '11 at 19:50
  • @ildjarn: Yes -- I'd misread the code; it does (correctly) return `input+n`. – Jerry Coffin Aug 26 '11 at 20:23