Sometimes you’d like to create your own manipulators, and it turns out to be remarkably simple. A zero-argument manipulator such as endl is simply a function that takes as its argument an ostream reference and returns an ostream reference. The declaration for endl is.
ostream& endl(ostream&);
Now, when you say:.
cout << "howdy" << endl;
the endl produces the
ostream& ostream::operator<<(ostream& (*pf)(ostream&)) {
return pf(*this);
}
The actual definition is a little more complicated since it involves templates, but this code illustrates the technique. When a function such as *pf (that takes a stream parameter and returns a stream reference) is inserted into a stream, this applicator function is called, which in turn executes the function to which pf points. Applicators for ios_base, basic_ios, basic_ostream, and basic_istream are predefined in the standard C++ library.
To illustrate the process, here’s a trivial example that creates a manipulator called nl that is equivalent to just inserting a newline into a stream (i.e., no flushing of the stream occurs, as with endl):.
//: C04:nl.cpp
// Creating a manipulator
#include
using namespace std;
ostream& nl(ostream& os) {
return os << '\n';
}
int main() {
cout << "newlines" << nl << "between" << nl
<< "each" << nl << "word" << nl;
} ///:~
When you insert nl into an output stream, such as cout, the following sequence of calls ensues:
cout.operator<<(nl) è nl(cout)
The expression
os << '\n';
inside nl( ) calls ostream::operator(char), which of course returns the stream, which is what is ultimately returned from nl( ).[45]
Effectors
As you’ve seen, zero-argument manipulators are easy to create. But what if you want to create a manipulator that takes arguments? If you inspect the
//: C04:Effector.cpp
// Jerry Schwarz's "effectors"
#include
#include
#include
#include
using namespace std;
// Put out a prefix of a string:
class Fixw {
string str;
public:
Fixw(const string& s, int width)
: str(s, 0, width) {}
friend ostream&
operator<<(ostream& os, const Fixw& fw) {
return os << fw.str;
}
};
// Print a number in binary:
typedef unsigned long ulong;
class Bin {
ulong n;
public:
Bin(ulong nn) { n = nn; }
friend ostream& operator<<(ostream& os, const Bin& b) {
const ulong ULMAX = numeric_limits
ulong bit = ~(ULMAX >> 1); // Top bit set
while(bit) {
os << (b.n & bit ? '1' : '0');
bit >>= 1;
}
return os;
}
};
int main() {
string words =
"Things that make us happy, make us wise";
for(int i = words.size(); --i >= 0;) {
ostringstream s;
s << Fixw(words, i);
assert(s.str() == words.substr(0, i));
}
ostringstream xs, ys;
xs << Bin(0xCAFEBABEUL);
assert(xs.str() ==
"1100""1010""1111""1110""1011""1010""1011""1110");
ys << Bin(0x76543210UL);
assert(ys.str() ==
"0111""0110""0101""0100""0011""0010""0001""0000");
} ///:~
The constructor for Fixw creates a shortened copy of its char* argument, and the destructor releases the memory created for this copy. The overloaded operator<< takes the contents of its second argument, the Fixw object, inserts it into the first argument, the ostream, and then returns the ostream so that it can be used in a chained expression. When you use Fixw in an expression like this:.
cout << Fixw(string, i) << endl;
a