Topic: C++ typing not so strong


Author: cok@islsun.Kodak.COM (David Cok)
Date: 13 Feb 91 13:40:20 GMT
Raw View
I and others recently completed a package of C++ classes in which we tried to
provide both strong typing and inheritance and we ran into the following
situation, in which the two requirements conflicted.

We had a base class Image from which some derived classes DoubleImage,
FloatImage, IntImage etc. were derived.  We defined lots of relevant
functions including (for example) operator- .

Now intuitively operator- applied to a DoubleImage produces a new
DoubleImage value and similarly for the other derived classes.  With this and
strong typing in mind, we defined member functions of the form

  const DoubleImage& DoubleImage::operator-() const;
  const FloatImage&  FloatImage::operator-() const;

But we also want some "virtualness" here.  If we apply operator- to an Image
we should get a new Image, having applied the correct - operator depending on
the runtime derived type.  That is we want:

  const Image& Image::operator-() const;

The resulting partial program is

Example A:

class Image {
    public:
 virtual const Image& operator-() const = 0;
};

class DoubleImage: public Image {
    public:
 const DoubleImage& operator-() const;
};

BUT THIS IS ILLEGAL because the two operator- declarations do not return the
same type.

The best way we could find to give our desired interface was a kludge like this:

Example B:

class Image {
    public:
 virtual const Image& operator_minus() const = 0;
 inline const Image& operator-() const
  { return this->operator_minus(); }
};

class DoubleImage {
    public:
 const Image& operator_minus() const; // the real work is here
 inline const DoubleImage& operator-() const
  { return (DoubleImage&)(((Image*)this)->operator_minus());}
};

which has type-unsafe casts and twice the number of functions.

Question 1: Does anyone have a better suggestion?
Question 2: What is the rationale for requiring that the implementations of
 virtual functions in derived classes return exactly the same type as is
 declared in the base class?  It would seem sufficient to require
 that the declaration in a derived class return a type which can be
 converted (using at least some set of implicit conversions) to that
 used in the base class.  For example,
 if the base class returns Image& the derived class may return
 DoubleImage& ; if the base class returns Image*, the derived class
 may return DoubleImage* .

This simple relaxation of the rule would not break any existing C++
code.  The compiler can type-check everything just as well, and it would
help avoid the problem of needing casts ("casts considered harmful...")
to avoid types drifting to the top of the inheritance tree.
It would have simplified our code above greatly -- almost
every function needed a virtual internal function so that we could
get the correct interface and virtual behavior as well.

It does require the compiler to do a little work.  Consider the rule relaxed to
allow at least the pointer and reference conversions indicated above, and add to
example A above the corresponding declaration of FloatImage::operator-().
Applying operator- to a FloatImage& would simply return a FloatImage&.  Applying
operator- to an Image& needs to return an Image&.  The compiler needs to create
a version of FloatImage::operator- which converts its FloatImage& result to an
Image& and a version of DoubleImage::operator- which converts its DoubleImage&
result to an Image& (change all references to pointers if you like).  For these
conversions that is trivial; more complicated conversions (which may not be
allowed) or the presence of MI would mean some real work had to be done. In
effect, the compiler converts example A into example B, but does so in a
type-safe manner.  When the programmer does it with casts, type-checking is
lost.

David R. Cok
Eastman Kodak Company
cok@Kodak.COM
716-477-7086