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().