Problem: Unable to access names in templatized base class
In example:
class CompanyA {
public:
...
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
class CompanyB {
public:
...
void sendCleartext(const std::string& msg);
void sendEncrypted(const std::string& msg);
...
};
... // classes for other companies
class MsgInfo { ... }; // class for holding information
// used to create a message
template<typename Company>
class MsgSender {
public:
... // ctors, dtor, etc.
void sendClear(const MsgInfo& info) {
std::string msg; create msg from info;
Company c;
c.sendCleartext(msg);
}
void sendSecret(const MsgInfo& info) // similar to sendClear,
{ ... } //except calls c.sendEncrypted
};
// Derived class
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
...
void sendClearMsg(const MsgInfo& info) {
write "before sending" info to the log;
sendClear(info);// call base class function;
// this code will not compile!
write "after sending" info to the log;
}
...
};
Such compilers will complain that sendClear doesn’t exist.
Reason:
When compilers encounter the definition for the class template LoggingMsgSender
, they don’t know what class it inherits from. Because Company
is a template parameter that won’t be known until later (when LoggingMsgSender
is instantiated). Without knowing what Company
is, there’s no way to know what the class MsgSender<Company>
looks like.
Total Template Specialization: provide specialized template
Now suppose we have a new class CompanyZ that insists on encrypted communications:
class CompanyZ { // this class offers no
public: // sendCleartext function
...
void sendEncrypted(const std::string& msg);
...
};
The general MsgSender
template is inappropriate for CompanyZ
, because that template offers a sendClear
function that makes no sense for CompanyZ
objects.
Solution:
template<> // a total specialization of
class MsgSender<CompanyZ> { // MsgSender; the same as the
public: // general template, except
// sendClear is omitted ...
void sendSecret(const MsgInfo& info) {...}
};
Note the template <>
syntax at the beginning of this class definition. It signifies that this is neither a template nor a standalone class. Rather, it’s a specialized version of the MsgSender
template to be used when the template argument is CompanyZ
. This 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.
If we recall the previous LoggingMsgSender
class, it makes no sense when the base class is MsgSender<CompanyZ>
, because that class offers no sendClear
function. That’s why C++ rejects the call: it recognizes that base class templates may be specialized and that such specializations may not offer the same interface as the general template.
Solution : disable C++’s “don’t look in templatized base classes” behavior.
- preface calls to base class functions with
this->
:template<typename Company> class LoggingMsgSender: public MsgSender<Company> { public: ... void sendClearMsg(const MsgInfo& info) { write "before sending" info to the log; this->sendClear(info); // okay, assumes that // sendClear will be inherited write "after sending" info to the log; } ... };
- employ a
using
declaration, a solution that should strike you as familiar if you’ve read Item 33.template<typename Company> class LoggingMsgSender: public MsgSender<Company> { public: using MsgSender<Company>::sendClear;// tell compilers to assume ... // that sendClear is in the // base class void sendClearMsg(const MsgInfo& info) { ... sendClear(info); // okay, assumes that // sendClear will be inherited ... } ... };
- explicitly specify that the function being called is in the base class:
This is generally the least desirable way to solve the problem, because if the function being called is virtual, explicit qualification turns off the virtual binding behavior.template<typename Company> class LoggingMsgSender: public MsgSender<Company> { public: void sendClearMsg(const MsgInfo& info) { ... MsgSender<Company>::sendClear(info);// okay, assumes that // sendClear will be // inherited ... } ... };
Note
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.
If the promise turns out to be unfounded, there will be problem.
LoggingMsgSender<CompanyZ> zMsgSender;
MsgInfo msgData;
... // put info in msgData
zMsgSender.sendClearMsg(msgData); // error! won’t compile
the call to sendClearMsg
won’t compile, because at this point, compilers know that the base class is the template specialization MsgSender<CompanyZ>
Things to Remember
- 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.