TITLE: Covariance of return types PROBLEM: William M Miller (wmm@world.std.com), 30 Jun 92 [ ... stuff deleted ... William Miller is (was) a member of the ANSI X3J16 C++ standards committe. ] The recently-accepted extension to allow virtual return types to differ in a covariant fashion... RESPONSE: David Shang (shang@corp.mot.com), 30 Jun 92 [ ... stuff deleted ...] The concept comes from Cardelli's sub-function law: to define a subfunction type, the domain of its output should be narrowed while the domain of its input should be widened. The reason for widening input domain is that any input acceptable to a super function type should also be acceptable to its subtypes, otherwise a function variable of super type may get erroneous input, if the variable get a value of a special sub-function type. For example: AA: Animal->Animal; RR: Reptile->Reptile; RR is not a subtype of AA, otherwise we can have assignment: AA:=RR; According to AA's interface, m:=AA(m) should have no problem. But this is wrong because AA acturally is a function of the type Reptile->Reptile. The principle of widening input domain forms a sharp contrast to the class-subclass structure. It would not be acceptable that a super function type be defined in a subclass while a sub-function type, in super class. The interface of a method defined in the subclass would be wrong by the contravariant rule. For example, we can not declare the method put in class ReptileSet to accept any animal, or even any object. Even we do not consider the the class-subclass structure, we also have practical reason to have RR as the subfuction type of AA. We sometimes wish to abstract a group of function types as a pattern with an isomorphic input output structure. For example, a function takes one object and produces a new object of the same type. But the principle of widening input domain meanwhile narrowing output domain prohibits defining a general function type to include all the subtypes with an isomorphic structure, since neither can RR be subtype of AA, nor can vice versa. Now we get a dilemma: applying contravariant rule on input type seems to be of very limited practical value (I'll apprieciate if anyone can give me an convincing example), while on the other hand applying covariant rule on input type will lead to a loophole in a strongly-typed system (many papers have discussed the loophole). I aggree that the covariant rule for output type is of a certain practical value, but its value is very limited without incorporating a covariant rule on the input type and a covariant rule on the type of data members. I would say this proposal is incomplete. Just take a simple example, if we consider the operator+ applied on two numbers with the same type: class Number { public: virtual Number operator+ (Number); }; and in a derived class Float, we certainly wish to redefine the interface of "+" to: class Float: public Number { public: virtual Float operator+ (Float); }; Only narrowing the output is not a complete refinement: class Float: public Number { public: virtual Float operator+ (Number); }; Or in other words, such refinement is not precise. I would like to borrow the concept of "mytype" in POOL-I, or "ThisClass" in Beta, or "like current" in Eiffel, or "self new class" in SmallTalk. Though the concept can help only in the case that the input or output type is the same as the type of the enclosing object, it is a complete solution in this case and has the practical value. The above example can written as follows: class Number { public: virtual thisclass operator+ (thisclass); }; and in the derived class Float, the interface of "+" will be refined automatically to Float X Float -> Float. The concept of "thisclass" can be generalized. It can be generalized to describe the type of an input or output, or the type of a data memnber is dependent on the type of its enclosing object (not just the same). It can be generalized to describe things like: thisclass's member type (if thisclass is a vector), thisclass's food type(if thisclass is a animal class), thisclass's member type's food type (if thisclass is an animal group class) etc. Therefore, the idea to make both the input and output type be narrowed is possible. This has been done in many research works. Give an example: type substitution by J. Palsberg and M. Schwartzbach in OOPSLA/ECOOP'90. And C++ template also has a potential to be improved to meet the requirement. I'm pretty sure this covariant output type proposal will be eventually substituted by a comprehensive solution. And such comprehensive solution is just coming! RESPONSE: jbuck@forney.berkeley.edu (Joe Buck), 1 Jul 92 In article <1992Jun30.231757.1114@cadsun.corp.mot.com> shang@corp.mot.com writes: >I did not read the document on covariant virtual return types. But I'm >suprised that the propopsal is accepted by the committee. >My question is that: why is the extension needed? is it a complete >solution to a concrete problem? and does it lead to demands for further >extension? Here's an example of why the extension was requested. Consider the following class: class ClonableObject { ... public: virtual ClonableObject* clone() const { return new ClonableObject(*this); } }; This class has, in effect, a virtual copy constructor. Given an object of this class or one derived from it, I can make one just like it by calling clone() on it. OK, now let's make a derived object: class DerivedClonable : public ClonableObject { ... public: ClonableObject* clone() const { return new DerivedClonable(*this); } }; Yes, it implements clone all right. But let's say I have a reference to a DerivedClonable and want to make a new one. I know by the way clone works that the returned pointer points to a DerivedClonable, even if the object is actually not a DerivedClonable but some further derived class. I suppose I could make a non-virtual function DerivedClonable* DerivedClonable::cloneDerivedClonable() const { return (DerivedClonable*)ClonableObject::clone(); } and then repeat this everywhere else in the inheritance hierarchy. Instead, the language modification permits the return type of the derived class to be a derived type of the return type of the base class, thus: class DerivedClonable : public ClonableObject { ... public: DerivedClonable* clone() const { return new DerivedClonable(*this); } }; Note that this clone still overrides the other clone. It completely and cleanly solves the problem. clone(), together with a master list containing one prototype instance of each of a large number of different classes, is one way of building an extensible system that cleanly supports incremental linking. Unfortunately, C++ still provides no way to automatically generate these clone methods for every object in an inheritance hierarchy (where the rest of the class is arbitrary -- templates don't quite cut it here).