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", ""));