People are mostly familar with explicit interface, let’s use an example
1 | void doProcessing(Widget& w) { |
What we can say about w
is that it must support the Widget interface. We can look up this interface in the sourse code of Widget
to see what it looks like. This is explicit interface - one explicitly visible in the source code.
In above case, because Widget
can be a base class with virtual functions, w
‘s calls to those functions will exhibit runtime polymorphism: the specific function to call will be determined at runtime based on w
‘s dynamic type.
As you would imagine, we can pass objects of following classes to above function.
1 | class Widget { |
The world of templates and generic programming is fundamentally different. In that world, explicit interfaces and runtime polymorphism continue to exist, but they’re less important. Instead, implicit interfaces and compile-time polymorphism move to the fore. To see how this is the case, look what happens when we turn doProcessing from a function into a function template:
1 | template<typename T> |
The implicit interface for T
(w
‘s type) appears to have these constraints:
- It must offer a member function named size
- There must be a
operator>
function that compares two objects, the one returned byw.size()
and int.
Pay attention to the wording, I didn’t say T
must has a size function that returns integral type. That’s what you will expect for explicit interface without template. Implicit interfaces are simply made up of a set of valid expressions, which exist only in developer’s head. The expressions themselves may look complicated, but the constraints they impose are generally straightforward.
So what object can I pass to doProcessingTP
? One of made up example is
1 | class RandomType { |
I intentionally make size
not return std::size_t
, but an arbitrary type, RandomType
itself. And as long as I provide a comparison function which compares RandomType
and int
, I can pass an instance of RandomType
to doProcessingTP
.
If you tries to pass the same object to following function, you will get compilation error
1 | void doProcessing(RandomType& w) { |
The error will be invalid operands to binary expression ('RandomType' and 'int')
. Because there is no implicit convertion from RandomType
to int
.
Things to remember:
- Both classes and templates support interfaces and polymorphism.
- For classes, interfaces are explicit and centered on function signatures. Polymorphism occurs at runtime through virtual functions.
- For template parameters, interfaces are implicit and based on valid expressions. Polymorphism occurs during compilation through template instantiation and function overloading resolution.