In derived class templates, refer to names in base class templates via a this->
prefix, via using
declarations, or via an explicit base class qualification.
Case Study
Sometimes when we cross from Object-oriented C++ to Template C++ (item 1), inheritance seem to stop working. For example, we’d like to log some information for the sendMsg
function in base class MsgSender
, so we make following derived class LoggingMsgSender
:
|
|
Note that the message-sending function in the derived class has a different name sendClearMsg
from its counterpart sendClear
in the base class: this is good design for two reasongs:
- it avoids the issue of hiding inherited names (item 33)
- it side-steps the problems inherent in redefining an inherited non-virtual function (item 36)
However, the code above will not compile, because, by default, compilers will not look for the function sendClear
in the base class. The behavior seems to break our expected inheritance concept from Object-oriented world, but compilers do this for a good reason: they will not know what the base class MsgSender<Company>
really looks like until when LoggingMsgSender
is instantiated, so it is possible that there’s a special template parameter class CompanyC
that, instead of providing function sendClearText
, only supports sendEncrypted
:
|
|
In this case, sendClear
function in MsgSender
base class will make no sense. To rectify the problem, we can create a specialized version of MsgSender
for CompanyZ
:
|
|
This syntax is known as a total template specialization: the template MsgSender
is specialized for the type CompanyZ
, and the specialization is total - once the type parameter has been defined to be CompanyZ
, no other aspect of the template’s parameters can vary.
Since base class templates may be specialized and that such specializations may not offer the same interface as the general template, C++ generally refuses to look in templatized base classes for inherited names. That’s why we say inheritance stops working in Template C++ wolrd.
To force C++ to look in templatized base classes, there are three ways:
1. Call with “this->”
|
|
2. Employ a “using” declaration
This is the same trick we use in item 33, which explains how using
declaration brings hidden base class names into a derived class’s scope1:
|
|
3. Explicitly specify the base class qualification
|
|
This approach is less desirable, because if the function being called is virtual, explicit qualification turns off the virtual binding behavior.
Summary
From a name visibility point of view, each of these approaches does the same thing: it promises compilers that any subsequent specializations of the base class template will support the interface offered by the general template. However, if the promise turns out to be unfounded, the subsequent compilation will still diagnose invalid references to base class members and reveal the truth:
|
|
Fundamentally, compilers will diagnose invalid references to base class members sooner (when derived class template definitions are parsed) or later (when templates are instantiated with specific template arguments). C++’s policy is to prefer early diagnoses, and that’s why it assumes it knows nothing about the contents of base classes when those classes are instantiated from templates.
-
Note that there’s difference between the problem solved in this item from that in item 33: here, the problem is that compilers don’t search base class scopes unless we explicitly specify it; in item 33, it is due to the derived class names that hide base class names. ↩︎