- Both Sections: Monday, December 3
In addition to whatever partial credit you received on Question 5/6 in Exam 2, you can receive up to 20 more points by answering the other question in this take-home remedial exam.You may consult any text you wish to answer this problem. I will provide some references to useful resources. You are not to discuss the question with anyone else.
Work can be submitted via email or printout by deadline.
Select the question that you did not work on in the exam.
Templates are required in C++ because of compile-time type checking. Generic classes (or metaclasses) could not be written otherwise. The dynamic type feature of Smalltalk allows you to write generic classes without a template special form.
The C++ type-checking happens not when you define a template, but when you instantiate a template, i.e., supply actual type arguments to the template (to replace the type parameters).
At that time, C++ substitutes all the occurrences of the type parameters with the supplied type arguments and verifies that they are used according to their definition. For built-in types (such as int or float), there are no methods to look for, just overloaded functions, but their presence is checked for. When a class is supplied as a type argument, any method call sites that appear in the template's methods are checked: checked to see if the calls agree with the interface of the class (argument). This allows the programmer to define generic methods that call methods on instances of the type argument(s).
Without template support, such generic methods could not be safely written in C++, since there is no specific class available that you could use in the variables inside your methods. This is not an obstacle in Smalltalk because the variables are not typed. At runtime, when the code is actually checked, any method references will be resolved against the class of the object in the variable.
Note that having a global common superclass (akin to Smalltalk's Object class) is not a sufficient substitute for template, because you do not generally want to be restricted to that class's simple interface. Object is fine for generic classes that call no methods on the argument type (simple collection classes like Queue, Stack, Vector and List), but not for ones where you need to call upon class-specific behavior. Good examples would be Set or Association List, where you need an acceptable definition of equivalence, and Sorted List, where you need some sort of ordering operator.
Java has a common superclass (Object) like Smalltalk, but has compile-time type checking like C++. A template mechanism is sorely missing from its language definition, although that deficit is being corrected.
Multiple inheritance in C++ is not just available for programmer convenience. It is required for the situations where you need to create a class that can operate (and type-check!) as an instance of two disparate classes. Under Smalltalk, with dynamic typing, the feature is not critical, and one can create classes that act like they directly inherit from multiple classes without a formal language mechanism like that provided by C++.
When you inherit from a class (in C++ or Smalltalk), you inherit not just the representation and the methods (which are overrideable), but the interface as well. By the interface, I mean the set of method signatures (method name, argument and return types). You can extend the interface by declaring/defining new methods. (When you override a method, you are not extending the interface.) When type checking, C++ compares all the method call sites in the source code against the relevant class interfaces. It identifies the interface to compare with by consulting the static type (the type as declared in the source) of the variable being used. At runtime, the actual type of the object bound to the variable can be a subclass of that static type, but since all subclasses are required (by definition) to support the interface, there is no risk.
When a class multiple inherits from two classes (let's call them A and B), it inherits two interfaces, and supports the union of the two. Instances of this new class can run on C++ codes that expect instances of A or expect instances of B.
Smalltalk doesn't do compile-time type checking. Variables are untyped, and so there is no static type to check against. Not until the call site is encountered at runtime is the existence/interface of a method (for an instance) checked against the call. So, so long as the class of the instance at runtime has the right method defined on it (i.e., the right name, the right number of arguments), the program will run error-free.
Let's reuse the example from class. Imagine we have two classes, LawnMower and Tractor, and we want to create a new class, RidingMower, that satisfies the interfaces of both LawnMower and Tractor. In Smalltalk, we can satisfy one of the interface or the other easily by inheriting, but we can also satisfy the interface of the other by manually defining (overloading) the methods in question. Programs written with either of the interfaces in mind would work fine.
One easy way to implement such a scheme is to represent the non-inherited class as an instance variable and forward the methods to the internal instance (this is often called delegation). So, for our example, our RidingMower class could inherit from Lawnmower, have an instance variable that is initialized with a Tractor instance when the RidingMower is initialized, and define methods for Ride(), FillUp(), ReplaceOil(), etc. that forward their arguments (if any) to the instance variable.
This approach isn't possible in C++ because overloading and overriding are two different things to the compiler. Support you define two disparate classes (not related through inheritance) with the same method and method signature. Two print() methods for instance. You can't (simply) write a program that work on instances of both these classes because there is no common static type you could use to get it to type-check.
(And no, your answer did not have to be this long!)