Part E - Polymorphism

Overview of Polymorphism

Identify the role of types in Polymorphism
Distinguish static and dynamic types
Describe the four different kinds of Polymorphism

"A type provides a protective covering that hides the underlying representation and constrains the way objects may interact with other objects" (Cardelli, Wegner, 1985)

Languages | Types | Categories | Summary | Exercises


The third principal feature of any object-oriented language alongside encapsulation and inheritance is polymorphism.  Polymorphism refers to the multiplicity of meanings attached to a particular name.  Polymorphic stands for 'of many forms'.  Polymorphism distinguishes different meanings for the name through type association. 

This chapter describes the concept of type in a bit more detail and its role in object-oriented languages.  The chapter also outlines the four categories of polymorphism that the C++ language supports.


Languages

Programming languages have evolved from untyped beginnings through monomorphic languages to polymorphic ones.  Untyped languages support words of one fixed size.  Assembly languages and BCPL, a predecessor of C, are examples.  Monomorphic languages support regions of memory of different sizes and distinguish them by attaching a specific type.  Once assigned, an object's type cannot change throughout its lifetime.  Polymorphic languages relax this monomorphic relation between a region of memory and a type by introducing ambiguity.  A polymorphic object may have different types during its lifetime, which brings it closer to our natural language usage. 

Monomorphic languages require separate code for each type of object.  For instance, a monomorphic language requires the programmer to code a separate sort() function for each data type, even though the logic is identical across all types.  A polymorphic language, on the other hand, requires the programmer to code the function once and the langauge applies it to any type. 

The C++ language supports polymorphism.  It assumes that an object is monomorphic, but lets the programmer set aside this default by explicitly identifying the object as polymorphic. 


Types

Raw memory consists of bit strings.  These bit strings may represent variables, objects, addresses, instructions, constants, etc.  Without knowing what a bit string represents, the compiler does not know how to interpret its value.  The compiler first needs to know what the bit string represents.  By associating a name, which stands for a region of memory, with a type we inform the compiler how to interpret the bit string in that region.  In other words, a type specifies how to interpret the bit string in a named region of memory. 

For example, once we associate harry with a Student and define the structure of a Student, the compiler knows that the first 4 bytes of harry hold his student number, stored in equivalent binary form and the remaining bytes hold his grades. 

Type Checking

A type system enables the compiler to check consistency between coding syntax and the type system.  Each type admits its own set of operations in forming expressions.  The compiler ensures consistency with the type system by rejecting all operations outside this set.  Breaking the type system would expose the underlying raw memory and the uncertainty of how to interpret its contents. 

A strongly typed language enforces type consistency at compile-time and postpones type-checking to run-time only if for polymorphic objects.  C++ is a strongly typed langauge.  That is, it checks for type consistency on monomorphic objects at compile-time and on polymorphic objects at run-time. 

Static and Dynamic Type

A polymorphic object can change its type throughout its lifetime.  We call this type its dynamic type.  An object's dynamic type differs from its static type.  Its static type is the reference type defined at compile-time, which does not change throughout the object's lifetime.  Its dynamic type is defined at run-time and may change throughout its lifetime. 

C++ implements polymorphic objects through pointers to them.  The pointer type defines the static type.  We define the dynamic type whenever we allocate dynamic memory for the object; that is, when we specify the constructor to be called in (re-)creating the object. 


Categories

The polymorphic features of an object-oriented language can be classified into four distinct categories.

Classifications

Background

Christopher Strachey (1967) introduced the concept of polymorphism into programming languages by informally distinguishing functions that work

  • differently on different argument types
  • uniformly on a range of argument types 

He defined the former as ad-hoc polymorphism and the latter as parametric polymorphism:

"Ad-Hoc polymorphism is obtained when a function works, or appears to work, on several different types (which may not exhibit a common structure) and may behave in unrelated ways for each type.  Parametric polymorphism is obtained when a function works uniformly on a range of types; these types normally exhibit some common structure." (Strachey, 1967)

polymorphism

Full Classification

Cardelli and Wegner (1985) expanded Strachey's distinction to accommodate object-oriented languages.  They distinguished functions that work on

  • a finite set of different and potentially unrelated types
    • coercion
    • overloading
  • a potentially infinite number of types across some common structure
    • inclusion
    • parametric

adhoc polymorphism

Inclusion polymorphism is specific to object-oriented languages.

Ad-Hoc Polymorphism

Ad-hoc polymorphism is apparent polymorphism.  That is, its polymorphic character disappears at closer scrutiny. 

Coercion

Coercion is a semantic operation that avoids a type error.  If the compiler encounters a mismatch between the type of an argument and the tpye of the parameter that receives the value of that argument, the compiler responds by converting the type of the argument to match the type of the corresponding parameter in the function definition.  The function definition only ever works on one type - the parameter type. 

C++ implements coercion at compile time.  If a compiler succeeds in matching the type of an argument to the type of the corresponding parameter in the function call, the compiler inserts the conversion code between the original argument(s) and its coerced type before the function call. 

Coercion may

  • narrow the argument type
  • widen the argument type (promotion)

For example,

 // Ad-Hoc Polymorphism - Coercion
 // polyCoercion.cpp

 #include <iostream>

 // One function definition:

 void display(int a) const {
     std::cout << "One argument (" << a << ')'; 
 }

 int main( ) {

     display(10);
     std::cout << std::endl;
     display(12.6); // narrowing
     std::cout << std::endl;
     display('\n'); // widening
     std::cout << std::endl;
 }













 One argument (10)

 One argument (12)

 One argument (10)


Most programming languages support coercion to some extent.  For instance, C narrows and promotes argument types in function calls so that one function will accept a limited variety of argument types. 

Overloading

Overloading is a syntactic abbreviation that associates one function name with a variety of function definitions.  That is, the same function name can accept a variety of unrelated sets of types.  Each set is associated with a function definition.  The compiler binds a function call with a particular signature to the function definition that matches that signature. 

No common logic has to exist amongst the definitions of overloaded functions.  Uniformity is a coincidence rather than a rule.  The definitions contain distinct and possibly totally unrelated logic.  Each definition works only on its set of types.  The number of sets is limited by the number of definitions implemented in the source code. 

C++ compilers implement overloading at compile time through monomorphic functions, one for each set of types.  The compiler mangles the function's name to include the signature so that the linker can bind the function call to the appropriate function definition. 

For example,

 // Ad-Hoc Polymorphism - Overloading
 // polymorphismOverloading.cpp

 #include <iostream>

 // Two function definitions:

 void display() const {
     std::cout << "No arguments";
 }

 void display(int a) const {
     std::cout << "One argument (" << a << ')'; 
 }

 int main( ) {

     display();
     std::cout << std::endl;
     display(10);
     std::cout << std::endl;
 }

















 No arguments

 One argument (10)


C does not admit overloading and requires a unique name for each function definition. 

Universal Polymorphism

Universal polymorphism is true polymorphism.  That is, its polymorphic character survives at closer scrutiny. 

Universal polymorphism imposes no restriction on the acceptable types.  It exhibits uniformity: the same function (logic) applies to a potentially unlimited range of different types. 

Inclusion

Inclusion polymorphism covers types belonging to an inheritance hierarchy.  Inclusion refers to the relation of one type to another: one type includes the other type.  Inclusion polymorphism distinguishes an object's dynamic type from its static type and postpones binding a call to a member function until the object's dynamic type is available. 

A polymorphic object can support several definitions that augment its static core.  Each definition is associated with a different dynamic type.  C++ binds a call to the definition that is associated with the object's dynamic type at run-time. 

The next chapter describes inclusion polymorphism in detail. 

Parametric

Parametric polymorphism refers to function and class definitions that share identical structure.  The definitions are common to all types.  The types need not be related in any way. 

C++ implements parametric polymorphism at compile-time using template syntax. 

The penultimate chapter describes how to apply template syntax to function definitions.


Summary

  • an object's static type is defined at compile-time and does not change throughout its lifetime
  • an object's dynamic type is defined at run-time and may change throughout its lifetime
  • ad-hoc polymorphism is only apparent - any relation between function definitions is coincidental
  • overloading associates the same function name with different and unrelated definitions
  • universal polymorphism is true polymorphism - a common structure exists regardless of type
  • parametric polymorphism applies to functions and classes that share identical structure across an unlimited number of unrelated types
  • inclusion polymorphism refers to types related by inclusion in an inheritance hierarchy and sharing some common structure

Exercises




Previous Reading  Previous: Derived Class with a Resource Next: Virtual Functions   Next Reading


  Designed by Chris Szalwinski   Copying From This Site   
Logo
Creative Commons License