Writing (Your Own) Stream Manipulators in C++
Streams are typically more than just dumping or loading grounds for basic data. Their strength lies in accommodation of new data types and customisation. As objects their state may also be controlled via either a method or a manipulator interface; I am going to concentrate on manipulators. I'll wrap up the article with a worked example.
There are two kinds of manipulators: those that accept arguments and those that don't. Manipulators that take no arguments are easy to write. All you have to do is write a function that accepts a stream parameter, does something to it , and returns it. Writing a manipulator that takes one or more arguments is more complicated because you need to create additional classes and functions ...
Simple manipulators
To write a manipulator that doesn't take an argument, write a function that takes an std::iostream
reference, does some work on the stream, and then returns it.
Data flow and control are the two basic operations on a stream. Returning references and the syntactic sugar of operator overloading offer a convenient way to chain together successive items for input or output in a single expression:
Besides std::endl
, other simple manipulators include: ws
, to act as a sink and eat whitespace from an istream; ends
, to NUL terminate a string; flush
, to flush an ostream; and hex
, dec
and oct
, to convert input from and output to the appropriate base.
These are all simply function names. In addition to the insertion and extraction member operators for basic types, the istream and ostream classes both have operators that take a function pointer to execute on the current stream.
Given this, it is possible to define your own manipulators to control layout and stream state:
ostream & tab(ostream &out) { return out << '\t'; }
or, more sophisticated:
istream & eatline(istream &in) { while(in && in.get() != '\n') { } return in; }
These manipulators may be used both indirectly and directly:
cin >> eatline; eatline(cin);
(The indirect form offers the syntactic convenience of mixing control and data into a sequence of insertions or extractions from a stream.)
Actually, the STL declarations that enable this behaviour are:
istream istream::operator>> (istream&, istream& (*f) (istream&)); istream istream::operator>> (istream&, ios&(*f) (ios&));
and
ostream ostream::operator<< (ostream&, ostream&(*f) (ostream&)); ostream ostream::operator<< (ostream&, ios&(*f) (ios&));
Parameterised manipulators
One approach is define a overload the (global) insertor or extractor for its second argument. If the insertor or extractor needs to read protected o private member variables, the class should declare the insertor or extractor a friend
. For example:
#include <iostream> struct fw { explicit constexpr fw( unsigned int w, char f = ' ') : width(w), fill(f) {} const unsigned int width; const char fill ; template < typename CHAR, typename TRAITS > inline friend std::basic_ostream<CHAR,TRAITS>& operator<< ( std::basic_ostream<CHAR,TRAITS>& stm, const fw& manip ) { stm.width( manip.width ); stm.fill( stm.widen( manip.fill ) ) ; return stm.flush() ; } }; int main() { std::cout << fw(10,'+') << 1234 << fw(8) << 567 << '\n'; }
A variation of this would be for struct fw
to define a member interter like
template < typename CHAR, typename TRAITS > inline friend std::basic_ostream<CHAR,TRAITS>& operator<< ( std::basic_ostream<CHAR,TRAITS>& stm) { stm.width(this->width ); stm.fill( stm.widen(this->fill)); return stm.flush(); }
Now, could we insert an object from a class that defines a member operator()(STREAM&) as below?
class MyClass { // ... public: std::ostream& operator() (std::ostream& o) { // ... return o; };
No. Unfortunately, this wouldn't work.