Polymorphism C and Data Structures Baojian Hua bjhuaustc

Polymorphism C and Data Structures Baojian Hua bjhua@ustc. edu. cn

An old dream in CS n Famous slogans: n n n Polymorphism n n “Write code once, used everywhere!” “Never write same code twice!” Poly + morphism Elegant and amazing combination of theory and practice in CS!

Why polymorphism? // a list of integer: struct List_t_int { int data; struct List_t_int *next; }; // “length” int length (List_t_int l) { int size=0; List_t_int temp = l->next; while (temp) { size++; the “data” field? temp=temp->next; } return size; }

Why polymorphism? // what about if we want a list of string? struct List_t_string { char *data; struct List_t_string *next; }; // function “length” remains the same? int length (List_t_string l) { int size=0; List_t_string temp = l->next; while (temp) { size++; temp=temp->next; } return size; }

Outline n n n Ad-hoc poly’ Parametric poly’ Subtype poly’

Ad-hoc poly’ n Functions operate on different but a limited set of data type n e. g. , “+” in C n n int * int, double * double, char * * int but not: char * * char * First introduced by Strachey in 1967 Evolve into a feature called “overloading” in modern OO languages

Ad-hoc poly’ n Overloading in modern OO languages: n Same printing function name with different argument types: n n n Compilers will dispatch function calls n n void print (int x); void print (char *s); print (5); print (“hello”); Bad news is that C does not support overloading! n n We’d have to do some hacking. : -( Or we can write unique names, as: n n void Int_print (int x); void String_print (char *s);

Parametric poly n n n Basic idea: decorating data types (data structure + functions) with type variables Polymorphic data structures Polymorphic functions n we start with this

E. g. int max (int x, int y) { return x>y? x: y; } // but we’ve observed the problem: double max (double x, double y) { return x>y? x: y; } // What about factor out the type names: X max (X x, X y) { return x>y? x: y; } // but this function does not type check!

Old trick #define max(x, y) x>y? x: y // So we can write: int m = max(3, 4); // or double n = max (3. 14, 2. 71); // but this is very good! Why? // We can do better by hoisting “X” further: #define max_X(X) X max (X x, X y) { return x>y? x: y; } size

Client code #define max_X(X) X max (X x, X y) { return x>y? x: y; } Questions: max_X(int) int main () { max (3, 4); } 1: what about we write this? max_X (double) does this program still type check? 2: what about we write max_X (char *) is char * comparable? is this algorithm correct?

Client code #define max_X(X) X max (X x, X y, int (*m)(X, X)) { if (1==m(x, y)) return x; return y; } max_X(int) int_compare (int x, int y) {…} int main () { max (3, 4, int_compare); }

List revisit #define List_X(X) struct List_t { X data; struct List_t *next; }; // “length” int length (List_t l) { int size=0; List_t temp = l->next; while (temp) { size++; temp=temp->next; } return size; } the “data” field?

Client code // we want a list of integer: List_X(int) // we want a list of double: List_X(double)

Summary so far n Parametric poly: n n n data structures and functions take extra type parameters (hence the name) instation at compile-time Monomorphinization: n n code eventually becomes monomorphic faithfully models the feature of template in C++

Summary so far n Pros: n n very powerful and elegant no runtime overhead (code is eventually mono) of course, require some C hack Cons: n code explosion (due to monomorphinization) n n n but seldom observed in practical code the poly code itself is not type checked compilation may be slow

Subtype poly struct List_t { X data; struct List_t *next; }; // Key idea: what if we can invent a most general // type “X”, such that any value is compatible // with “X”. // In C, this type is “void *”.

List revisit struct List_t { void *data; struct List_t *next; }; // Leave it an exercise to write function “length”

Client code int main () { // we want a list of integer: for (int i=0; i<10; i++) { int *p = malloc (sizeof (*p)); *p = i; List_insert. Head (l, p); } // we want a list of double: for (int i=0; i<10; i++) { double *p = malloc (sizeof (*p)); *p = i; List_insert. Head (l, p); } }

Subtype poly n All pointer types can be treated as a subtype of void * n n so we’d have to heap-allocate all objects to accompany this type Java or C# go even further: all objects are heap-allocated n n the most general type is Object this models Java generic honestly

Subtype poly n Pros: n n n single piece of code no compilation overhead Cons: n Safety issue n n efficiency issue n n ugly type cast, and must be veryyyyyyy careful allocation and garbage collection complexity issue

Think abstractly struct List_t { int data; struct List_t *next; }; // Is it a good idea to write this function? int exists (List_t l, int x); // Or this function? int sum (List_t l, int init); // Or this one? void print (List_t l);

Think abstractly void print (List_t l) { List_t temp = l->next; while (temp) { printf (“%d, ”, temp->data) temp = temp->next; } return; }

Think abstractly // But what if we make it parametric poly? #define List_X(X) struct List_t { X data; struct List_t *next; }; void print (List_t l);

1 st try void print (List_t l) { List_t temp = l->next; while (temp) { printf (“%? ? ? ”, temp->data); temp = temp->next; } return; }

Think abstractly // To make it parametric poly: #define List_X(X) struct List_t { X data; struct List_t *next; }; // so we’d have to make “print” more abstract! void print (List_t l, void (*p)(X));

2 nd try void print (List_t l, void (*p)(X)) { List_t temp = l->next; while (temp) { p (temp->data); temp = temp->next; } return; }

Client code // instantiate an “int” list: List_X(int) void f (int x) { printf (“%d, ”, x); } int main () { List_t list = …; List_print (list, f); } // cook a list of int
- Slides: 28