Classes support explicit interfaces based on function signatures, as well as runtime polymorphism through virtual functions; templates support implicit interfaces based on valid expressions, as well as compile-time polymorphism through template instantiation and function overloading resolution.
Interface and Polymorphism in OOP
The world of object-oriented programming revolves around explicit interfaces and runtime polymorphism. For example:
|
|
|
|
Regarding the w
in doProcessing
, we could conclude:
- explicit interface:
w
is of typeWidget
, so it must support theWidget
interface, which is an explicit interface, because it is explicitly visible in the source code (e.g., the .h file forWidget
) - runtime polymorphism:
w
’s call to some of its virtual member functions exhibits runtime polymorphism, because the specific function to call will be determined at runtime based onw
’s dynamic type (item 37)
Specifically, the explicit interface of Widget
consists of its function signatures: a constructor, a destructor, and the functions size
, normalize
, and swap
, along with the parameter types, return types, and constnesses of these functions, as well as compilter-generated copy constructor and copy assignment operator (item 5). Potentially, it could also include typedefs, and data members.
Interface and Polymorphism in templates and generic programming
In the world of templates and generic programming, the explicit interfaces and runtime polymorphism continue to exist, but we also have to consider implicit interfaces and compile-time polymorphism. As a comparison, let’s take a look at the template version of doProcessing
:
|
|
For w
in the template doProcessing
:
- implicit interface:
w
must support all the operations performed on it, but there is no explicit function signatures to follow. Rather, there’s an implicit interface consists of valid expressions that set constraints onw
. - compile-time polymorphism:
w
is one of the parameters inoperator>
andoperator!=
, which may involve instantiating function templates with different template parameters, leading to different functions being called during compilation. This is similar to the process to determine which of a set of overloaded functions should be called during compilation.
Specifically, let’s take a look those constrains on w
’s type T
in the implicit interface:
- whatever
w.size() > 10 && w != someNasyWidget
yields, the expression as a whole must be compatible withbool
- calls to the copy constructor, to
normalize
, and toswap
must be valid for objects of typeT
However, inside the expression w.size() > 10 && w != someNasyWidget
, constraints on compatibal type regarding the functions size
, operator>
, operator&&
, or operator!=
are pretty flexible, thanks to the possibility of operator overloading and implicit conversion:
- there’s no requirement that
size
returns an integral value - it may simply return an object of some typeX
such that there is anoperator>
that can be called with an object of typeX
and anint
(10 is of type int) - there’s no requirement that
operator>
take a parameter of typeX
- it may take a parameter of typeY
, as long as there were an implicit conversion from objects of type X to objects of typeY
- there’s no requirement that
T
supportoperator!=
- it would be just as acceptable foroperator!=
to take one object of typeX
and one object of typeY
, as long asT
can be converted toX
andsomeNastyWidget
’s type can be converted toY
- potentially, even
operator&&
could be overloaded, changing the meaning of the above expression from a conjunction to something quite different
Anyway, the implicit interfaces imposed on a template’s parameters are just as real as the explicit interfaces imposed on a class’s objects, and both are checked during compilation.