Using std::copy for STL Container Output and Input
Here are a couple of concise constructs that come in handy when debugging C++ code involving STL containers.
Output
The following line of code prints the contents of an STL container cont whose value_type is T to the std::cout stream, where each element is separated by a space.
#include <algorithm> #include <iterator> #include <iostream> std::copy(cont.begin(), cont.end(), std::ostream_iterator<T>(std::cout, " "));
It works for any type T for which the insertion operation (operator<<) and assignment operation (operator=) have been defined.
Unfortunately this last statement means that this technique does not work if cont is a std::map. The std::map::value_type is a key-value std::pair, and the STL does not define an inserter for std::pair. Because both std::ostream_iterator and std::pair are in the std namespace the operator<< must also be there for argument dependent lookup (Koenig lookup) to find it. So you must use another technique for dumping the contents of a std::map to a stream (such as a functor and std::for_each()).
Input
Here is a line of code that reads values of type T from the input stream cin and stores them in an STL container cont
#include <iterator> #include <algorithm> #include <iostream> std::copy(std::istream_iterator<T>(cin), std::istream_iterator<T>(), std::back_inserter(cont));
The second argument to std::copy, the default iterator, represents the end of the input. This code works as long as an extractor (operator>>) is defined for type T, and as long as you use an inserter appropriate for the container type. The third argument above, the inserter, can be std::inserter(), std::back_inserter or std::front_inserter, depending on whether the container type provides an insert(), push_back() or push_front() method, and depending on how you want to fill the container.
The same caveat about the std::map applies to input as applies to output. Because the std namespace lacks an extractor for the std::map::value_type std::pair, and because you cannot add one, legally, to the std namespace, you cannot use std::copy to extract std::map elements from an input stream. You must use a different approach.
An Example Using a Custom Type
Here we define a custom type Point, a two-dimensional coordinate, so we need to provide an inserter and an extractor. The extractor shown below is inspired by the example in "The C++ Programming Language, Third Edition", Bjarne Stroustrup, Addison-Wesley, 1997 (ISBN 0-201-88954-4), in Section 21.3.5, "Input of User-Defined Types".
The example uses a std::set to contain the Point objects. The second template parameter for a std::set is the predicate used for ordering the elements, which defaults to std::less. We therefore must implement operator< for our Point class (or specify a non-default predicate).
#include <algorithm> #include <iterator> #include <iostream> #include <set> // or vector, or list, or...(not map!) struct Point { float x; float y; Point() : x(0.0f), y(0.0f) { } Point(float xx, float yy) : x(xx), y(yy) { } // Compiler supplies copy-constructor and assignment operator. // So we can store Points in a std::set. bool operator<(const Point& other) const { return ((x < other.x) || (!(other.x < x) && y < other.y)); } }; // // Inserter // std::ostream& operator<<(std::ostream& s, const Point& p) { return s << '(' << p.x << ',' << p.y << ')'; } // // Extractor -- assume the format produced by the inserter. // std::istream& operator>>(std::istream& s, Point& p) { // A Point must be expressed as "(x,y)" (whitespace is ignored). float x = 0.0f, y = 0.0f; char c = '\0'; bool got_a_point = false; s >> c; if (c == '(') { s >> x >> c; if (c == ',') { s >> y >> c; if (c == ')') { got_a_point = true; } } } else { s.putback(c); } if (got_a_point) { p.x = x; p.y = y; } else { s.clear(std::ios_base::badbit); } return s; } //---------------------------------------------------------------- int main() { std::set<Point> input; // // Read from standard input until encountering non-Point data. // std::cout << "Enter some points in the form '(x,y)': " << std::endl; std::copy(std::istream_iterator<Point>(std::cin), std::istream_iterator<Point>(), std::inserter(input, input.begin())); // // Write back to standard output, one Point per line. // std::cout << "You entered: " << std::endl; std::copy(input.begin(), input.end(), std::ostream_iterator<Point>(std::cout, "\n")); return 0; }
Note that the std::set has an insert() method, but not a push_back() or push_front() method. We must therefore use a std::inserter() as the third argument to std::copy to insert the user input into a std::set. The std::inserter requires two arguments, the second being a valid iterator for the container into which we are inserting. Since the std::set orders its elements by key (value) the insertion point is really just a suggestion. Because std::set iterators remain valid after insertion we can use any valid input iterator. For an empty std::set the only valid iterators are begin() and end().
- Fred's blog
- Login to post comments
- Printer-friendly version
