Search code examples
c++stliteratoristream-iterator

Limiting the range for std::copy with std::istream_iterator


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.


Solution

  • 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.