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.