Effective C++ item 43: Know How to Access Names in Templatized Base Classes

Regarding calling functions in base class, there is a difference between how non-template polymorphism and template polymorphism. This is because we have total template specialization in template world which will confuse compiler unless we are specific about it. Let me show you an example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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){}
};

template<typename Company>
class MsgSender {
public:
void sendClear(const std::string& msg)
{
Company c;
c.sendCleartext(msg);
}

void sendSecret(const std::string& msg){
Company c;
c.sendEncrypted(msg);
}
};

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear;
void sendClearMsg(const std::string& msg)
{
//write "before sending" info to the log;
sendClear(msg); // <-------- won't compile!
//write "after sending" info to the log;
}
};

You might be surprised that calling sendClear won’t compile. There is a reason compiler gives you this error. Imagine you also have following code

1
2
3
4
5
6
7
8
9
10
class CompanyC {
public:
void sendEncrypted(const std::string& msg){}
};

template<>
class MsgSender<CompanyC> {
public:
void sendSecret(const std::string& msg){}
};

Where CompanyC doesn’t have sendCleartext method and a total template specialization version of the class MsgSender for CompanyC which doesn’t have sendClear method. Now you will appreciate your compiler thinking ahead of you previously because now it really doesn’t make sense to compile!

There are three ways to tell compiler that please assume sendClear always exists.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
using MsgSender<Company>::sendClear;
void sendClearMsg(const std::string& msg)
{
sendClear(msg);
}
};

template<typename Company>
class LoggingMsgSender: public MsgSender<Company> {
public:
void sendClearMsg(const std::string& msg)
{
this->sendClear(msg);
}
};

// This is not preferred 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 std::string& msg)
{
MsgSender<Company>::sendClear(msg);
}
};

You can argue that compiler can detect this in two places

  1. when derived class template definitions are parsed
  2. when those templates are instantiated with specific template arguments
    You are correct. But 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.

Reference:
“Effective C++” Third Edition by Scott Meyers.