This one is entertaining, and useful too. We all know ("we" being C++ programmers who came from C or still use C extensively) that scanf will eat up a format nice and pretty:
scanf ( "%d-%d-%d", &m, &d, &y );
If the input stream does not contain an integer followed by a '-' followed by an integer (shush Dave, and humor me), followed by a '-' followed by an integer, the call will fail. Sadly, this useful feature isn't available in C++ unless you use scanf. Using iostreams, you would be forced to do something painful like this (with suitable error handling, of course):
cin>> m;
if ( cin.peek() == '-' )
cin.ignore();
cin>> d;
if ( cin.peek() == '-' )
cin.ignore();
cin>> y;
Wouldn't it be nice if we could just do this and be done with it?
cin>> m >>"-">> d >>"-">> y;
Well, unfortunately it's not quite that easy. One could overload operator>> for a pointer to const char, but that kind of invades the implementation's personal space and therefore, is not portable.
To do it would be trivial though:
istream& operator>> ( istream& in, const char *s )
{
while ( *s != '\0' && in && in.peek() == *s ) {
in.get();
++s;
}
if ( *s != '\0' )
in.setstate ( ios::failbit );
return in;
}
This was my solution (more or less). I didn't need something portable, and the benefit of clarity was a decisive factor. Looking toward portability though, just as a mental exercise, I also considered the manipulator approach:
class scan {
public:
scan ( const char *init ): fmt ( init ) {}
friend istream& operator>> ( istream& in, const scan& s )
{
while ( *s.fmt != '\0' && in && in.peek() == *s.fmt ) {
in.get();
++s.fmt;
}
if ( *s.fmt != '\0' )
in.setstate ( ios::failbit );
return in;
}
private:
mutable const char *fmt;
};
It's almost as simple, but using it requires a bit more work:
cin>> m >> scan ( "-" ) >> d >> scan ( "-" ) >> y;
On a similar note, I also found a similar manipulator useful. The following manipulator will discard all characters in the stream up to (and including) a user defined character delimiter:
class moveto {
public:
moveto ( istream::int_type init ): ch ( init ) {}
friend istream& operator>> ( istream& in, const moveto& d )
{
if ( in.rdbuf()->in_avail() > 0 )
in.ignore ( numeric_limits<streamsize>::max(), d.ch );
return in;
}
private:
istream::int_type ch;
};
This is slightly more complicated than the usual "empty the buffer" call to ignore, designed to flush the stream of interactive input. That job could be performed with a simple moveto request:
But, moveto is useful in parsing as well.
One can easily imagine how useful these manipulators can be. Not only does the scanf behavior of eating a format work with standard input and output, a clever programmer could easily pair them with stringstreams, fstreams, or even custom streams to solve a whole slew of formatted parsing problems without hacking together something big and ugly.
And with a little bit of extra work, the scan manipulator can be extended to more accurately mimic scanf's format processing.