Обратите внимание на синтаксическую конструкцию «template» в начале определения класса. Она означает, что это и не шаблон, и не автономный класс. Это специализированная версия шаблона MsgSender, которая должна использоваться, если параметром шаблона является CompanyZ. Называется это
Имея специализацию шаблона MsgSender для CompanyZ, снова рассмотрим производный класс LoggingMsgSender:
template typename Company
class LoggingMsgSender: public MsgSenderCompany {
public:
...
void sendClearMsg(const MsgInfo info)
{
sendClear(info); // если Company == CompanyZ,
// то этой функции не существует
}
...
};
Как следует из комментария, этот код просто не имеет смысла, если базовым классом является MsgSenderCompanyZ, так как в нем нет функции sendClear. Поэтому C++ отвергнет такой вызов; компилятор понимает, что шаблон базового класса можно специализировать, и интерфейс, предоставляемый этой специализацией, может быть не таким, как в общем шаблоне. В результате компилятор обычно не ищет унаследованные имена в шаблонных базовых классах. В некотором смысле, когда мы переходим от «объектно-ориентированного C++» к «C++ с шаблонами» (см. правило 1), наследование перестает работать.
Чтобы исправить ситуацию, нужно как-то заставить C++ отказаться от догмы «не заглядывай в шаблонные базовые классы». Добиться этого можно тремя способами. Во-первых, можно предварить обращения к функциям из базового класса указателем this:
template typename Company
class LoggingMsgSender: public MsgSenderCompany {
public:
...
void sendClearMsg(const MsgInfo info)
{
;
this-sendClear(info); // порядок! Предполагается, что
// sendClear будет унаследована
;
}
...
};
Во-вторых, можно воспользоваться using-объявлением. Мы уже обсуждали эту тему в правиле 33, где было сказано, что using-объявлением делает скрытые имена из базового класса видимыми в производном классе. Поэтому мы можем переписать sendClearMsg следующим образом:
template typename Company
class LoggingMsgSender: public MsgSenderCompany {
public:
using MsgSenderCompany::sendClear; // сообщает компилятору о том, что
... // sendClear есть в базовом классе
void sendClearMsg(const MsgInfo info)
{
...
sendClear(info); // нормально, предполагается, что
... // sendClear будет унаследована
}
...
};
Хотя using-объявление будет работать как здесь, так и в правиле 33, но используются они для решения разных задач. Здесь проблема не в том, что имена из базового класса скрыты за именами, объявленными в производном классе, а в том, что компилятор вообще не станет производить поиск в области видимости базового класса, если только вы явно не попросите его об этом.
И последний способ заставить ваш код компилироваться – явно указать, что вызываемая функция находится в базовом классе:
template typename Company
class LoggingMsgSender: public MsgSenderCompany {
pubilc:
...
void sendClearMsg(const MsgInfo info)
{
...
MsgSenderCompany::sendClear(info); // нормально, предполагается, что
... // sendClear будет унаследована
}
...
};
Но этот способ хуже прочих, посколько если вызываемая функция виртуальна, то явная квалификация отключает динамическое связывание.