Solution
The answers to the three questions are as
follows.
-
Is it safe to
inherit ci_char_traits from
char_traits<char> this way?
Public inheritance should normally model IS-A /
WORKS-LIKE-A as per the Liskov Substitution Principle (LSP). (See
Items 22
and 28.)
This, however, is one of the rare exceptions to the LSP, because
ci_char_traits is not intended to be used polymorphically
through a pointer or reference to the base class
char_traits<char>. The standard library does not use
traits objects polymorphically. In this case, inheritance is used
merely for convenience (well, some would say laziness); inheritance
is not being used in an object-oriented way.
On the other hand, the LSP does still apply in
another sense: It applies at compile-time, when the derived object
must WORK-LIKE-A base object in the ways required by the
basic_string template's requirements. In a newsgroup
posting, Nathan Myers puts it this way:
In other words, LSP
applies, but only at compile-time, and by the convention we call
"requirements lists." I would like to distinguish this case; call
it Generic Liskov Substitution Principle (GLSP): Any type (or
template) passed as a template argument should conform to the
requirements listed for that argument.
Classes derived from
iterator tags and traits classes, then, are subject to GLSP, but
classical LSP considerations (for example, virtual destructor, and
so forth) may or may not apply, depending on whether run-time
polymorphic behaviors are in the signature specified in the
requirements list.
So, in short, this inheritance is safe, because
it conforms to GLSP (if not LSP). However, the main reason I used
it here was not for convenience (to avoid writing all the other
char_traits<char> baggage), but to demonstrate
what's different—that we had to
change only four operations to get the effect we want.
The reason behind this first question was to get
you to think about several things: (1) the proper uses (and
improper abuses) of inheritance; (2) the implications of the fact
that there are only static members; (3) the fact that
char_traits objects are never used polymorphically.
-
Why does the
following code fail to compile?
ci_string s = "abc";
cout << s << endl;
Hint: From 21.3.7.9 [lib.string.io] in
the C++ standard, the declaration of operator<< for
basic_string is specified as:
template<class charT, class traits, class Allocator>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os,
const basic_string<charT,traits,Allocator>& str);
Answer: Notice first that cout is
actually a basic_ostream<char, char_traits<char>
>. Then we can spot the problem: operator<<
for basic_string is templated and all, but it's specified
only for insertion into a basic_ostream with the same
"char type" and "traits type" as the string. That is, the
standard operator<< will let you output a
ci_string to a basic_ostream<char,
ci_char_traits>, which isn't what cout is, even
though ci_char_traits inherits from
char_traits<char> in the above solution.
There are two ways to resolve this: Define
operator<<() and operator>>() for
ci_strings yourself, or tack on ".c_str()" to use
operator<<( const char* ) if your application's
strings don't have embedded nulls:
cout << s.c_str() << endl;
-
What about
using other operators (for example, +, +=,
=) and mixing strings and ci_strings as
arguments? For example:
string a = "aaa";
ci_string b = "bbb";
string c = a + b;
Again, there are two ways to deal with this:
Either define your own operator+() functions, or tack on
".c_str()" to use operator+( const char* ):
string c = a + b.c_str();
|