N3581
2013-03-16
Mike Spertus, Symantec
mike_spertus@symantec.com

Delimited iterators

It is extremely tempting to use ostream_iterator to, say, print a vector like: vector<int> v = {1, 4, 6}; cout << "("; copy(v.begin(), v.end(), ostream_iterator<int>(cout, ", ")); cout << ")"; // Oops! Prints (1, 4, 6, )

The problem is that the “delimiter” in the ostream_iterator constructor call is better described as a suffix than a delimeter.

We offer two alternate proposals

Option 1

Add a constructor to ostream_iterator that also takes a boolean argument saying whether you want a suffix. We can now easily print a vector correctly: vector<int> v = {1, 4, 6}; cout << "("; copy(v.begin(), v.end(), ostream_iterator<int>(cout, ", ", false)); cout << ")"; // Prints (1, 4, 6) as desired

Option 2

In case we would rather not modify ostream_iterator, we could add a new delimited_iterator class that acts like ostream_iterator except that the delimiter is only placed between output elements: vector<int> v = {1, 4, 6}; cout << "("; copy(v.begin(), v.end(), delimited_iterator<int>(cout, ", ")); cout << ")"; // Prints (1, 4, 6) as desired

Implementation

Straightforward. I give my students a homework assignment to implement delimited_iterator every year.

Wording for option 1

Modify §24.6.2 [ostream.iterator] as follows:
ostream_iterator is defined as:
namespace std {
  template <class T, class charT = char, class traits = char_traits<charT> >
  class ostream_iterator:
    public iterator<output_iterator_tag, void, void, void, void> {
  public:
 	  typedef charT char_type;
    typedef traits traits_type;
    typedef basic_ostream<charT,traits> ostream_type;
    ostream_iterator(ostream_type& s);
    ostream_iterator(ostream_type& s, const charT* delimiter);
    ostream_iterator(ostream_type& s, const charT* delimiter, bool is_suffix);
    ostream_iterator(const ostream_iterator<T,charT,traits>& x);
   ~ostream_iterator();
    ostream_iterator<T,charT,traits>& operator=(const T& value);
    ostream_iterator<T,charT,traits>& operator*();
    ostream_iterator<T,charT,traits>& operator++();
    ostream_iterator<T,charT,traits>& operator++(int);
  private:
    basic_ostream<charT,traits>* out_stream; // exposition only
    const charT* delim; // exposition only
    bool suf; // exposition only
    bool first_element; // exposition only
  };
}
Modify §24.6.2.1 [ostream.iterator.cons.des] as follows:
ostream_iterator(ostream_type& s, const charT* delimiter);
Effects: Initializes out_stream with &s, and delim with delimiter, suf with false, and first_element with true.
ostream_iterator(ostream_type& s, const charT* delimiter, bool is_suffix);
Effects: Initializes out_stream with &s, delim with delimiter, suf with is_suffix, and first_element with true.
ostream_iterator(const ostream_iterator& x);
Effects: Constructs a copy of x.
Modify §24.6.2.2 [ostream.iterator.ops] as follows:
ostream_iterator& operator=(const T& value);
Effects:
if(delim != 0 && !suf) {
  if(!first_element)
   out_stream << delim;
  first_element = false;
}
*out_stream << value;
if(delim != 0 && suf)
  *out_stream << delim;
return (*this);

Wording for option 2

Modify §24.3 [iterator.synopsis] as follows:
template <class T, class charT = char, class traits = char_traits<charT> >
class ostream_iterator;
template <class T, class charT = char, class traits = char_traits<charT> >
class delimited_iterator;
template<class charT, class traits = char_traits<charT> >
class istreambuf_iterator;
Add a section §24.6.x named “Class template delimited_iterator” [delimited.iterator] between §24.6.2 [ostream.iterator] and §24.6.3 [istreambuf.iterator]. This section should be the same as §24.6.2 with all occurrences of ostream_iterator replaced with delimited_iterator mutatis mutandis except as follows: Make §24.6.x [delimited.iterator] as follows:
delimited_iterator is defined as:

namespace std {
  template <class T, class charT = char, class traits = char_traits<charT> >
  class delimited_iterator:
    public iterator<output_iterator_tag, void, void, void, void> {
  public:
 	  typedef charT char_type;
    typedef traits traits_type;
    typedef basic_ostream<charT,traits> ostream_type;
    ostream_iterator(ostream_type& s);
    delimited_iterator(ostream_type& s, const charT* delimiter);
    delimited_iterator(const ostream_iterator<T,charT,traits>& x);
   ~delimited_iterator();
    delimited_iterator<T,charT,traits>& operator=(const T& value);
    delimited_iterator<T,charT,traits>& operator*();
    delimited_iterator<T,charT,traits>& operator++();
    delimited_iterator<T,charT,traits>& operator++(int);
  private:
    basic_ostream<charT,traits>* out_stream; // exposition only
    const charT* delim; // exposition only
    bool first_element; // exposition only
  };
}
The corresponding portion of §24.6.x.1 [delimited.iterator.cons.des] should look as follows:
delimited_iterator(ostream_type& s, const charT* delimiter);
Effects: Initializes out_stream with &s, delim with delimiter, and first_element with true.
delimited_iterator(ostream_type& s, const charT* delimiter, bool is_suffix);
Effects: Initializes out_stream with &s, delim with delimiter and first_element with true.
delimited_iterator(const delimited_iterator& x);
Effects: Constructs a copy of x.
The corresponding portion §24.6.x.2 [delimited.iterator.ops] should look as follows:
delimited_iterator& operator=(const T& value);
Effects:
if(delim != 0) {
  if(!first_element)
   out_stream << delim;
  first_element = false;
}
*out_stream << value;
return (*this);