Channels ▼
RSS

C/C++

An Enhanced ostream_iterator


std::ostream_iterator

The standard (C++-03: 24.5.2) defines the interface of the std::ostream_iterator class template, as shown in Listing Three.

Listing Three: Definition of std::ostream_iterator

// In namespace std
template< typename V            // The type to be inserted
    , typename C = char         // Char encoding of stream
    , typename T = std::char_traits<C> // Traits type
    >
class ostream_iterator
 : public iterator<output_iterator_tag, void, void, void, void>
{ 
public: // Member Types
 typedef C                      char_type;
 typedef T                      traits_type;
 typedef std::basic_ostream<char_type, traits_type> ostream_type;
 typedef ostream_iterator<V, C, T>          class_type;
public: // Construction
 explicit ostream_iterator(ostream_type& os);
 ostream_iterator(ostream_type& os, char_type const* delim);
 ostream_iterator(class_type const& rhs);
 ~ostream_iterator() throw();
public: // Assignment
 class_type& operator =(V const& value);
public: // Output Iterator Methods
 class_type& operator *();
 class_type& operator ++();
 class_type operator ++(int);
private:
 . . .
} ;

This is a classic output iterator, whereby each instance remembers the stream and the (optional) delimiter from which it is constructed and uses them to effect formatted output when a value is assigned to it. Implementations typically maintain a pointer to the stream, which we'll call m_stm, and a copy of the delimiter pointer (i.e., of type char_type const*), which we'll call m_delim. Listing Four shows the implementation of the assignment operator.

Listing Four: Definition of the Assignment Operator

template<typename V, typename C, typename T>
ostream_iterator<V, C, T>&
 ostream_iterator<V, C, T>::operator =(V const& value)
{ 
 *m_stm << value;
 if(NULL != m_delim)
 { 
  *m_stm << m_delim;
 } 
 return *this;
} 

It's a really simple, clever idea with just one flaw: the lack of ambition demonstrated in the previous section.

void Difference Type

Note in Listing Three that ostream_iterator uses the standard manner of instantiating the std::iterator type generator (Section 12.2) class template, by which all types except the iterator category are void. These are defined as void to help prevent the use of output iterators in contexts where their behavior would be undefined. For example, the standard (C++-03: 24.3.4) defines the std::distance() algorithm's return type in terms of the given iterator's difference_type, as in the following:

template <typename I>
typename std::iterator_traits<I>::difference_type
 distance(I from, I to);

Since the evaluation of such distance for an output iterator is not meaningful, output iterators should define their difference_type (usually via std::iterator, as shown in Listing Three) to be void, which will precipitate a compilation error if a user tries to apply std::distance() to such types.

Define member types as void to (help) proscribe unsupported operations.

stlsoft::ostream_iterator

The STLSoft libraries contain a very modest enhancement to std::ostream_iterator, imaginatively called stlsoft::ostream_iterator. Its definition is shown in Listing Five.

Listing Five: Definition of stlsoft::ostream_iterator

// In namespace stlsoft
template< typename V
    , typename C = char
    , typename T = std::char_traits<C>
    , typename S = std::basic_string<C, T>
    >
class ostream_iterator
 : public std::iterator<std::output_iterator_tag
            , void, void, void, void>
{ 
public: // Member Types
 typedef V                      assigned_type;
 typedef C                      char_type;
 typedef T                      traits_type;
 typedef S                      string_type;
 typedef std::basic_ostream<char_type, traits_type> ostream_type;
 typedef ostream_iterator<V, C, T, S>        class_type;
public: // Construction
 explicit ostream_iterator(ostream_type& os)
  : m_stm(&os)
  , m_prefix()
  , m_suffix()
 { } 
 template <typename S1>
 explicit ostream_iterator(ostream_type& os, S1 const& suffix)
  : m_stm(&os)
  , m_prefix()
  , m_suffix(stlsoft::c_str_ptr(suffix))
 { } 
 template< typename S1
     , typename S2
     >
 ostream_iterator(ostream_type& os, S1 const& prefix, S2 const& suffix)
  : m_stm(&os)
  , m_prefix(stlsoft::c_str_ptr(prefix))
  , m_suffix(stlsoft::c_str_ptr(suffix))
 { } 
 ostream_iterator(class_type const& rhs)
  : m_stm(rhs.m_stm)
  , m_prefix(rhs.m_prefix)
  , m_suffix(rhs.m_suffix)
 { } 
 ~ostream_iterator() throw()
 { } 
public: // Assignment
 class_type& operator =(assigned_type const& value)
 { 
  *m_stm << m_prefix << value << m_suffix;
  return *this;
 } 
public: // Output Iterator Methods
 class_type& operator *()
 { 
  return *this;
 } 
 class_type& operator ++()
 { 
  return *this;
 } 
 class_type operator ++(int)
 { 
  return *this;
 } 
private: // Member Variables
 ostream_type* m_stm;
 string_type  m_prefix;
 string_type  m_suffix;
} ;

The main difference is the sole functional enhancement: separation of the delimiter into a prefix and a suffix. For full compatibility with std::ostream_iterator semantics, the second, two-parameter constructor specifies the suffix, not the prefix, since std::ostream_iterator inserts the delimiter into the stream after the value.

That's it for the interface. We will now examine the several implementation differences.

Shims, Naturally

The most obvious implementation difference is the use of string access shims. These afford all the usual flexibility to work with a wide variety of string or string-representable types. Indeed, all of the following parameterizations of the iterator are well formed:

std::string           prefix("prefix");
stlsoft::simple_string     suffix("suffix");
  
stlsoft::ostream_iterator<int> osi1(std::cout);
stlsoft::ostream_iterator<int> osi2(std::cout, "suffix");
stlsoft::ostream_iterator<int> osi3(std::cout, "prefix", "suffix");
stlsoft::ostream_iterator<int> osi4(std::cout, suffix);
stlsoft::ostream_iterator<int> osi5(std::cout, prefix, "");
stlsoft::ostream_iterator<int> osi6(std::cout, prefix, "suffix");
stlsoft::ostream_iterator<int> osi7(std::cout, "prefix", suffix);
stlsoft::ostream_iterator<int> osi8(std::cout, prefix, suffix);

Safe Semantics

The standard does not prescribe how std::ostream_iterator is to be implemented, which is usually perfectly reasonable. However, in this case there is no stipulation as to whether the iterator instance should take a copy of the delimiter contents or merely, as most implementations do, copy the pointer. As long as ostream_iterator is used in its familiar context, as a temporary passed to an algorithm, this is irrelevant. However, it's not hard to make a mess:

std::string         delim("\ n");
std::ostream_iterator<int> osi(std::cout, delim.c_str());
std::vector<int>      ints(10);
  
delim = "something else";
  
std::copy(ints.begin(), ints.end(), osi); // Undefined behavior!!

The issue can be compounded when using string access shims in the constructor to make it more generic. Consider what would happen if m_prefix and m_suffix were of type char_type const*, rather than of type string_type. It would be possible to use the iterator class template with any type whose shim function returns a temporary, as in the following:

std::vector<int> ints(10);
VARIANT      pre = . . . 
VARIANT      suf = . . . 
  
std::copy(ints.begin(), ints.end()
    , stlsoft::ostream_iterator<int>(std::cout, pre, suf)); // Boom!

This is not something obvious even to the trained eye. The problem is that the language requires that temporaries exist for the lifetime of their enclosing full expression (C++-03: 12.2;3). Let's look again at the requisite lines from the implementation, highlighting the shim invocations:

 template<typename S1, typename S2>
 ostream_iterator(ostream_type& os, S1 const& prefix, S2 const& suffix)
  : m_stm(&os)
  , m_prefix(stlsoft::c_str_ptr(prefix))
  , m_suffix(stlsoft::c_str_ptr(suffix))
 { } 

By the time the constructor completes, the temporary instances of the conversion class returned by the c_str_ptr(VARIANT const&) overload invocations have been created, had their implicit conversion operator called, and been destroyed. The m_prefix and m_suffix pointers would be left holding onto garbage. When these pointers are used within the std::copy() statement, it's all over, Red Rover!

This is a problem with wide potential, but thankfully we can avoid it by adhering to one simple rule.

Rule - If your constructor uses conversion (or access) shims, you must not hold onto the results of the shim functions in pointer (or reference) member variables.

This rule offers only three options. First, we could use (but not hold) the result pointer within the constructor body, as we did in the case of unixstl::glob_sequence (Section 17.3.5). Second, we could copy the result into a string member variable, as we did in the case of unixstl::readdir_sequence (Section 19.3.2).

The final option is to eschew the use of string access shims entirely and stick with C-style string pointers as constructor parameters. But this pushes all responsibility for conversion out to the application code, thereby violating the Principle of Composition (as far too many C++ libraries are wont to do).

Given that the IOStreams are anything but lightning-quick themselves, the prudent choice here is to err on the side of flexibility and safety. Thus stlsoft::ostream_iterator stores copies of the prefix and suffix arguments in member variables of string_type. The nice side effect of this is that the assignment operator implementation becomes extremely simple, just a single statement:

 *m_stm << m_prefix << value << m_suffix;

Don't emulate undefined vulnerabilities in the standard without careful consideration.

std::ostream_iterator Compatibility

The first of the two constructors provides full compatibility with std::ostream_iterator. The second, three-parameter constructor provides the additional functionality. To define a prefix-only iterator is straightforward: Just specify the empty string ("") as the third parameter in the three-parameter constructor:

std::copy(impls.begin(), impls.end()
    , stlsoft::ostream_iterator<srchseq_t::value_type>(std::cout
                             , "\ t", ""));


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video