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 floats
s (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 floats
s, 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.
Take a look at std::copy_n
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.
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;
}
}
精彩评论