Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
A wide stream is a stream class that handles wide
characters. All the examples so far (except for the last traits example in
Chapter 3) have used narrow streams that hold instances of char.
Since stream operations are essentially the same no matter the underlying
character type, they are encapsulated generically as templates. So all input
streams, for example, are connected to the basic_istream class template:
template<class charT, class traits =
char_traits<charT> >
class basic_istream {...};
In fact, all input stream types are specializations of this
template, according to the following type definitions:
typedef basic_istream<char> istream;
typedef basic_istream<wchar_t> wistream;
typedef basic_ifstream<char> ifstream;
typedef basic_ifstream<wchar_t> wifstream;
typedef basic_istringstream<char> istringstream;
typedef
basic_istringstream<wchar_t> wistringstream;
All other stream types are defined in similar fashion.
In a perfect world, this is all you d need to create streams
of different character types. But things aren t that simple. The reason is that
the character-processing functions provided for char and wchar_t
don t have the same names. To compare two narrow strings, for example, you use
the strcmp( ) function. For wide characters, that function is named
wcscmp( ). (Remember these originated in C, which does not have function overloading, hence unique names are required.) For this reason, a generic
stream can t just call strcmp( ) in response to a comparison operator. There needs to be a way for the correct low-level functions to be called automatically.
The solution is to factor out the differences into a new
abstraction. The operations you can perform on characters have been abstracted
into the char_traits template, which has predefined specializations for char
and wchar_t, as we discussed at the end of the previous chapter. To
compare two strings, then, basic_string just calls traits::compare( ) (remember that traits is the second template parameter), which in
turn calls either strcmp( ) or wcscmp( ), depending on
which specialization is being used (transparent to basic_string).
You only need to be concerned about char_traits if
you access the low-level character processing functions; most of the time you
don t care. Consider, however, making your inserters and extractors more robust
by defining them as templates, just in case someone wants to use them on a wide
stream.
To illustrate, recall again the Date class inserter
from the beginning of this chapter. We originally declared it as:
ostream& operator<<(ostream&, const Date&);
This accommodates only narrow streams. To make it generic,
we simply make it a template based on basic_ostream:
template<class charT, class traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT,
traits>& os,
const Date& d) {
charT fillc = os.fill(os.widen('0'));
charT dash = os.widen('-');
os << setw(2) << d.month << dash
<< setw(2) << d.day << dash
<< setw(4) << d.year;
os.fill(fillc);
return os;
}
Notice that we also have to replace char with the
template parameter charT in the declaration of fillc, since it
could be either char or wchar_t, depending on the template
instantiation being used.
Since you don t know when you re writing the template which
type of stream you have, you need a way to automatically convert character
literals to the correct size for the stream. This is the job of the widen( ) member function. The expression widen('-'), for example, converts its
argument to L - (the literal syntax equivalent to the conversion wchar_t( - ))
if the stream is a wide stream and leaves it alone otherwise. There is also a narrow( ) function that converts to a char if needed.
We can use widen( ) to write a generic version
of the nl manipulator we presented earlier in the chapter.
template<class charT, class traits>
basic_ostream<charT,traits>&
nl(basic_ostream<charT,traits>& os) {
return os << charT(os.widen('\n'));
}
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |