N4007
Revision of N3581
2014-05-25
Mike Spertus, Symantec
[email protected]
Nathan Wilson
[email protected]

Delimited iterators (Rev. 2)

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

Proposal

We propose a new ostream_joiner 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(), std::make_ostream_joiner(cout, ", ")); cout << ")"; // Prints (1, 4, 6) as desired

If we want the delimiter after each element, like in ostream_iterator, we can specify it as followsvector<int> v = {1, 4, 6}; // Prints 1, 4, and 6 on their own lines as desired copy(v.begin(), v.end(), std::make_ostream_joiner(cout, "\n", std::suffix));

Discussion

In the LEWG discussion of N3581 in Issaquah, it was pointed out that the interactions of this proposal were unexpectedly subtle, so the authors wish to discuss the design decisions in more detail.

Implementation

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

Wording

Modify §24.3 [iterator.synopsis] as follows:
//24.6, stream iterators:
enum delimiter_type {
  infix,
  suffix
};

template <class T, class charT = char, class traits = char_traits<charT>,
    class Distance = ptrdiff_t>
class istream_iterator;
template <class T, class charT, class traits, class Distance>
  bool operator==(const istream_iterator&;t'T,charT,traits,Distance>& x,
          const istream_iterator<T,charT,traits,Distance>& y);
template <class T, class charT, class traits, class Distance>
  bool operator!=(const istream_iterator<T,charT,traits,Distance>& x,
          const istream_iterator<T,charT,traits,Distance>& y);
          
template <class charT = char, class traits = char_traits<charT> >
    class ostream_iterator;
template <class charT = char, class traits = char_traits<charT> >
    class ostream_joiner;
template <class charT, class traits>
  ostream_joiner make_ostream_joiner(basic_ostream<charT, traits>& os, delimiter_style d = infix);
template<class charT, class traits = char_traits<charT> >
class istreambuf_iterator;
Add a section §24.6.x named “Class template ostream_joiner” [ostream.joiner] 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 ostream_joiner mutatis mutandis except as follows: Make §24.6.x [ostream.joiner] as follows:
ostream_joiner is defined as:

namespace std {
  template <class charT = char, class traits = char_traits<charT> >
  class ostream_joiner:
    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_joiner(ostream_type& s, const charT* delimiter, delimiter_style d = infix);
    ostream_joiner(const ostream_iterator<T,charT,traits>& x);
   ~ostream_joiner();
    template<typename T>
    ostream_joiner<charT,traits>& operator=(const T& value);
    ostream_joiner<charT,traits>& operator*();
    ostream_joiner<charT,traits>& operator++();
    ostream_joiner<charT,traits>& operator++(int);
  private:
    basic_ostream<charT,traits>* out_stream; // exposition only
    const charT* delim; // exposition only
    bool first_element; // exposition only
    delimiter_style style; // exposition only
  };
}
The corresponding portion of §24.6.x.1 [ostream.joiner.cons.des] should look as follows:
ostream_joiner(ostream_type& s, const charT* delimiter, delimiter_style d);
Effects: Initializes out_stream with &s, delim with delimiter, style with d and first_element with true.
ostream_joiner(const ostream_joiner& x);
Effects: Constructs a copy of x.
The corresponding portion §24.6.x.2 [ostream.joiner.ops] should look as follows:
template<typename T>
ostream_joiner& operator=(const T& value);
Effects:
if(delim != 0) {
  if(style== suffix || !first_element)
   *out_stream << delim;
  first_element = false;
}
*out_stream << value;
return (*this);
The corresponding portion of §24.6.x.3 [ostream.joiner.creation] should look as follows:
template <class charT, class traits>
  ostream_joiner make_ostream_joiner(basic_ostream<charT, traits>& os, delimiter_style d = infix);
Returns: ostream_joiner<charT, traits>(os, delimiter, d);