N4257
Revision of N4066
2014-11-04
Mike Spertus, Symantec
[email protected]
Nathan Wilson
[email protected]

Delimited iterators (Rev. 4)

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

Discussion

In the LEWG discussion of this proposal, a delimiter style such as prefix, infix, and suffix was presented.

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:
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 DelimT, class charT = char, class traits = char_traits<charT> >
    class ostream_joiner;
template <class charT, class traits, class DelimT>
  ostream_joiner<decay_t<DelimT>, charT, traits> make_ostream_joiner(basic_ostream<charT, traits>& os, DelimT&& delimiter);
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]: Make §24.6.x [ostream.joiner] as follows:
24.6.x     Class template ostream_joiner                        [ostream.joiner]

ostream_joiner writes (using operator<<) successive elements onto the output stream from which it was constructed. The delimiter that it was constructed with is written to the stream between every two Ts that are written. It is not possible to get a value out of the output iterator. Its only use is as an output iterator in situations like
while (first != last)
  *result++ = *first++;

ostream_joiner is defined as 
namespace std { template <class DelimT, class charT = char, class traits = char_traits<charT> > class ostream_joiner { public: typedef charT char_type; typedef traits traits_type; typedef basic_ostream<charT,traits> ostream_type; typedef output_iterator_tag iterator_category; typedef void value_type; typedef void difference_type; typedef void pointer; typedef void reference; ostream_joiner(ostream_type& s, DelimT&& delimiter); ostream_joiner(ostream_type& s, const DelimT& delimiter); template<typename T> ostream_joiner<DelimT, charT,traits>& operator=(const T& value); ostream_joiner<DelimT, charT,traits>& operator*(); ostream_joiner<DelimT, charT,traits>& operator++(); ostream_joiner<DelimT, charT,traits>& operator++(int); private: basic_ostream<charT,traits>* out_stream; // exposition only DelimT delim; // exposition only bool first_element; // exposition only }; }
24.6.x.1     ostream_joiner constructor              [ostream.joiner.cons]

ostream_joiner(ostream_type& s, const DelimT& delimiter);
Effects: Initializes out_stream with &s, delim with delimiter, and first_element with true.

ostream_joiner(ostream_type& s, DelimT&& delimiter);
Effects: Initializes out_stream with &s, delim with move(delimiter), and first_element with true.

24.6.x.2     ostream_joiner operations              [ostream.joiner.ops]

template<typename T> ostream_joiner<DelimT, charT, traits>& operator=(const T& value);
Effects:
if (!first_element)
   *out_stream << delim;
first_element = false;
*out_stream << value;
return (*this);

ostream_joiner<DelimT, charT, traits>& operator*();
Returns: *this

ostream_joiner<DelimT, charT, traits>& operator++();
ostream_joiner<DelimT, charT, traits>& operator++(int);
Returns: *this
24.6.x.3     ostream_joiner creation function              [ostream.joiner.creation]

template <class charT, class traits, class DelimT>
  ostream_joiner<decay_t<DelimT>, charT, traits> make_ostream_joiner(basic_ostream<charT, traits>& os, DelimT&& delimiter);
    
Returns: ostream_joiner<decay_t<DelimT>, charT, traits>(os, forward<DelimT>(delimiter));