Topic: Integral promotions


Author: lewis@sophists.com (Lewis G. Pringle)
Date: Sat, 19 Sep 1992 23:49:46 GMT
Raw View
In article <3670@hpwala.wal.hp.com> pabloh@hpwala.wal.hp.com (Pablo Halpern ) writes:
>I'm afraid not.  This is one of the biggest complaints I have with the
>"clarification" done to the rules of disambiguating functions calls.  As it
>stands now, defining a function in a derived class hides *all* functions of
>the same name in its base classes.  This means that if you override any
>foo() you must override all foo()s:
>
...
>
>This is more than a nuisance, it breaks inheritence and polymorpism.
>Changing my example slightly:
>
 There is really a rather simple solution to this problem. And the
solution also has the feature the feature that it lets you advertise more
clearly what the properties of overrides should be.

 If you have a base class SHAPE that you wish to respond polymorphicly
to the message MOVETO, then do so as follows:

 class SHAPE {
  public:
  ...
   void MOVETO(int x, int y)
   {
    MOVETO_(x, y);
    assert (GETPOS () == (x,y));
   }

  protected:
   virtual void MOVETO_ (int x, int y) = 0;
 };

 With this sort of technique, you can overload MOVETO as you wish,
and as subclasses override MOVETO_, it has no effect on the desired overloading.
Moreover, you also gain the benefit of having specified for documentation
purposes, and in a way the compiler can help enforce, the properties of
overrides of that function.

    Lewis.




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Sun, 20 Sep 1992 14:50:22 GMT
Raw View
In article <1992Sep19.234946.15510@sophists.com> lewis@sophists.com (Lewis G. Pringle) writes:
>In article <3670@hpwala.wal.hp.com> pabloh@hpwala.wal.hp.com (Pablo Halpern ) writes:
>>I'm afraid not.  This is one of the biggest complaints I have with the
>>"clarification" done to the rules of disambiguating functions calls.  As it
>>stands now, defining a function in a derived class hides *all* functions of
>>the same name in its base classes.  This means that if you override any
>>foo() you must override all foo()s:
>>
>...
> There is really a rather simple solution to this problem. And the
>solution also has the feature the feature that it lets you advertise more
>clearly what the properties of overrides should be.
>
> If you have a base class SHAPE that you wish to respond polymorphicly
>to the message MOVETO, then do so as follows:
>
> class SHAPE {
>  public:
>  ...
>   void MOVETO(int x, int y)
>   {
>    MOVETO_(x, y);
>    assert (GETPOS () == (x,y));
>   }
>
>  protected:
>   virtual void MOVETO_ (int x, int y) = 0;
> };
>
> With this sort of technique, you can overload MOVETO as you wish,
>and as subclasses override MOVETO_, it has no effect on the desired overloading.
> ...

Obviously you did not understand what the real problem was;
your example cannot help against it.
If any subclass of SHAPE defines, e.g.
 void MOVETO(float x, float y)
the original MOVETO becomes hidden, and that nuisance cannot be prevented
by any means in current C++.

It is hard to see what kind of benefit one could get from the additional
complication (extra level of indirection) in the example.

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: dlarsson@aut.abb.se (Daniel Larsson)
Date: 22 Sep 92 07:31:10 GMT
Raw View
In article <1992Sep20.145022.29462@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
>In article <1992Sep19.234946.15510@sophists.com> lewis@sophists.com (Lewis G. Pringle) writes:
>>In article <3670@hpwala.wal.hp.com> pabloh@hpwala.wal.hp.com (Pablo Halpern ) writes:
>>>I'm afraid not.  This is one of the biggest complaints I have with the
>>>"clarification" done to the rules of disambiguating functions calls.  As it
>>>stands now, defining a function in a derived class hides *all* functions of
>>>the same name in its base classes.  This means that if you override any
>>>foo() you must override all foo()s:
>>>
>>...
>> There is really a rather simple solution to this problem. And the
>>solution also has the feature the feature that it lets you advertise more
>>clearly what the properties of overrides should be.
>>
>> If you have a base class SHAPE that you wish to respond polymorphicly
>>to the message MOVETO, then do so as follows:
>>
>> class SHAPE {
>>  public:
>>  ...
>>   void MOVETO(int x, int y)
>>   {
>>    MOVETO_(x, y);
>>    assert (GETPOS () == (x,y));
>>   }
>>
>>  protected:
>>   virtual void MOVETO_ (int x, int y) = 0;
>> };
>>
>> With this sort of technique, you can overload MOVETO as you wish,
>>and as subclasses override MOVETO_, it has no effect on the desired overloading.
>> ...
>
>Obviously you did not understand what the real problem was;
>your example cannot help against it.
>If any subclass of SHAPE defines, e.g.
> void MOVETO(float x, float y)
>the original MOVETO becomes hidden, and that nuisance cannot be prevented
>by any means in current C++.
>
>It is hard to see what kind of benefit one could get from the additional
>complication (extra level of indirection) in the example.
>
>----------------------------------------------------------------------
>Markku Sakkinen (sakkinen@jytko.jyu.fi)
>       SAKKINEN@FINJYU.bitnet (alternative network address)
>Department of Computer Science and Information Systems
>University of Jyvaskyla (a's with umlauts)
>PL 35
>SF-40351 Jyvaskyla (umlauts again)
>Finland
>----------------------------------------------------------------------

As an aside, what Lewis tries to accomplish is similar to how the language
Beta does virtual method combination. It starts at the top of the hierarchy
and progresses down the inheritance hierarchy. I think there are merits with
this scheme, as Lewis says.

Of course, it does not solve the original problem.

--
---------------------------------------------------------------------------
Daniel Larsson          Email:    dlarsson@aut.abb.se
ABB Automation AB Telefax:   +46 21 34 25 25
V{ster}s, Sweden Telephone: +46 21 34 30 29




Author: lewis@sophists.com (Lewis G. Pringle)
Date: Tue, 22 Sep 1992 21:42:59 GMT
Raw View
In article <1992Sep20.145022.29462@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:
>In article <1992Sep19.234946.15510@sophists.com> lewis@sophists.com (Lewis G. Pringle) writes:
>>In article <3670@hpwala.wal.hp.com> pabloh@hpwala.wal.hp.com (Pablo Halpern ) writes:
>>>I'm afraid not.  This is one of the biggest complaints I have with the
>>>"clarification" done to the rules of disambiguating functions calls.  As it
>>>stands now, defining a function in a derived class hides *all* functions of
>>>the same name in its base classes.  This means that if you override any
>>>foo() you must override all foo()s:
>>>
>>...
>> There is really a rather simple solution to this problem. And the
>>solution also has the feature the feature that it lets you advertise more
>>clearly what the properties of overrides should be.
>>
>> If you have a base class SHAPE that you wish to respond polymorphicly
>>to the message MOVETO, then do so as follows:
>>
>> class SHAPE {
>>  public:
>>  ...
>>   void MOVETO(int x, int y)
>>   {
>>    MOVETO_(x, y);
>>    assert (GETPOS () == (x,y));
>>   }
>>
>>  protected:
>>   virtual void MOVETO_ (int x, int y) = 0;
>> };
>>
>> With this sort of technique, you can overload MOVETO as you wish,
>>and as subclasses override MOVETO_, it has no effect on the desired overloading.
>> ...
>
>Obviously you did not understand what the real problem was;
>your example cannot help against it.
>If any subclass of SHAPE defines, e.g.
> void MOVETO(float x, float y)
>the original MOVETO becomes hidden, and that nuisance cannot be prevented
>by any means in current C++.
>
>It is hard to see what kind of benefit one could get from the additional
>complication (extra level of indirection) in the example.
>
 Perhaps you find it hard to see because of our differing views of
how to use subclassing in C++.

 I generally use a base class like SHAPE to define an abstract
API, and then create concrete suclasses that follow that API. For one such
API class I might have 50 concrete subclasses implementing that API.
It is very often convenient to overload functions, and provide default parameters.
This technique allows you to do this once: in the class you are using to define
the public API (SHAPE - here). For me it is then a requirement that ALL concrete
subclasses override the MOVETO_ method to specify its behaviour (because it's pure-virtual).
It is in these contexts that I no longer have to worry about getting default
parameters and overloads exactly the same as in the base class.

 The example you give is a case where you wish to ADD NEW OVERLOADS,
and it is true, that the way C++ is defined, you may sometimes need to redundently
specify all the cases you wish overloaded (as painful as this may seem, there are
really good reasons for this behaviour too). Humorously enuf, the case you suggested,
ISN'T one of them. I now define a new ABSTRACT class - and refine my API:

 class HIGH_PRECISION_SHAPE : public SHAPE {
   void MOVETO(float x, float y)
   {
    MOVETO_(x, y);
   }

  protected:
   // All concrete subclasses now override this, INSTEAD of
   // the integer version below.
   virtual void MOVETO_ (float x, float y) = 0;

   /* Really I would do this next function non-inline*/
   virtual void MOVETO_ (int x, int y) { MOVETO_ ((float)x, (float)y); }
 };

 Notice here how I've monotonicly increased the public API of SHAPE,
to produce a class that will work in all the old contexts. If someone has
a SHAPE&, or a HIGH_PRECISION_SHAPE& - no matter: MOVETO (3, 3) still works.
With a HIGH_PRECISION_SHAPE& I can also say MOVETO (3, 3.0). Thus all the code
written to operate on SHAPEs works transparently with my new HIGH_PRECISION_SHAPEs.
Now I can subclass again:
 class HIGH_PRECISION_RECT : public HIGH_PRECISION_SHAPE {
  protected:
   virtual void MOVETO_ (float x, float y);
 };

 Note how there is nothing in the concrete subclasses that would
make more difficult changes to the overloads, etc, of my abstract bases (which
define the programming API).

 Further, I can now go back, months later, and make the following changes to
class SHAPE:
 struct Point { int x, int y; Point (int a, int b): x(a), y(b) };
 class SHAPE {
  public:
  ...
   void MOVETO(int x, int y)
   {
    MOVETO_(x, y);
    assert (GETPOS () == (x,y));
   }
   // MY ADDITION...
   void MOVETO(Point p)
   {
    MOVETO_(p.x, p.y);
   }

  protected:
   virtual void MOVETO_ (int x, int y) = 0;
 };

 Still, all my concrete subclasses of shape have the same effective public
API as SHAPE (except in cases like HIGH_PRECISION_SHAPE where MOVETOs public
API was hidden - here you have extra work - but this seems unavoidable since
you would probably want to define an FPoint class, and do an overload of MOVETO that took
this in HIGH_PRECISION_SHAPE).

 Anyway, extending the API the way you've suggested is a RARE operation.
In this case the technique I'm suggesting does not help much. But remember
it's only when your extending the public API, and only then when you want to
extend it by further overloading already defined parts of the API.

 In the FAR more common situations (at least for me) I am just overriding a
virtual method in a subclass (rendering the subclass concrete), and here I
don't need to worry about preserving the public API of the base class -
that is WHAT KIND OF BENEFIT ONE COULD GET FROM THE ADDITIONAL COMPLICATION
IN THE EXAMPLE.

 In addition to this, as I mentioned, an important part of the API of
one of these base classes are its pre and postconditions. C++ does not give
you a direct way of specifying these. This technique DOES. You specify the
pre and post conditions in the given NON-virtual public method, and then
all concrete subclases/overrides are guarnteed to follow this part of the API.
I find this quite valuable.

 BTW, your comment about "(extra level of indirection)" was correct, but
perhaps misleading. The public API method can be made (and for me always is)
inline, and the assertions, if you use them are compiled away when debugging
is turned off, and so there is NO runtime cost, or code size cost in using
this technique (though admittedly, there MAY be some extra typing).

 I could go on for hours about other implications of this approach,
but I'm running out of ink.

 I hope this clarifies my previous posting.

        Lewis.














--
Reading peoples signatures is a real waste of time.

lewis@sophists.com                                  (Lewis Gordon Pringle, Jr.)




Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: Wed, 23 Sep 1992 06:35:23 GMT
Raw View
In article <1992Sep22.214259.6922@sophists.com> lewis@sophists.com (Lewis G. Pringle) writes:
>In article <1992Sep20.145022.29462@jyu.fi> sakkinen@jyu.fi (Markku Sakkinen) writes:

> [very long explanation omitted]

This _is_ interesting.  Also, as pointed out by Daniel Larsson in the meantime,
your approach makes it possible to emulate the BETA kind of inheritance
somewhat.  Further, since virtuality is indirect or hidden from the public
interface, external clients cannot override it by explicit class qualification
(a possibility often regarded as a drawback of C++).

> Anyway, extending the API the way you've suggested is a RARE operation.
>In this case the technique I'm suggesting does not help much. But remember
>it's only when your extending the public API, and only then when you want to
>extend it by further overloading already defined parts of the API.

_You_ think that it's rare, but it was exactly the problem that the original
poster (Pablo Halpern, I think) complained about!  Your first posting
pretended to solve that, but did not.  Please do not become another
Piercarlo Grandi;  when answering to other people's questions, don't
twist those questions first.

----------------------------------------------------------------------
Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: steve@taumet.com (Steve Clamage)
Date: Sat, 12 Sep 1992 17:47:05 GMT
Raw View
jason@jarthur.claremont.edu (Jason Merrill) writes:

>Why is the conversion int -> long (or unsigned int -> unsigned long) not
>considered an integral promotion?

>I am in the situation of trying to take either a pair of longs (with
>default arguments) or a double for creation of a Fraction.  If I do
>something like

>Fraction(1),

>my compiler (BC++ 3.0) complains about ambiguity.  This seems wrong to me.

The "promotions" are from "smaller" types to int or unsigned int.
There are automatic conversions to long and double.  This is the
heritage from C, unchanged in C++.  That isn't much of a "why",
but I don't have a better answer.

In contrast to your question, some programmers complain about having
so many silent conversions, some of which they feel ought to be errors.

No conversion is preferred over any other conversion when resolving
a call to an overloaded function.  If you have only
 foo(long)
 foo(double)
and call foo() with an int argument, neither function is preferred
over the other, and the call is ambiguous.

You generally want to provide an overloaded function taking an int
parameter if you have any taking any other integral types, and
one taking a double parameter if you have any taking other
floating types.  You can do this:

inline T foo(int i) { return foo((long)i); } // foo(long) returns type T

(Hmmm.  T foo(long) sounds like a beverage.)

Make this NON-virtual in the base class, and don't override it in
any derived class.  You will then always get the correct version
of a virtual foo(long) every time you call foo with a (signed)
integer type, with no extra overhead.
--

Steve Clamage, TauMetric Corp, steve@taumet.com
Vice Chair, ANSI C++ Committee, X3J16




Author: pabloh@hpwala.wal.hp.com (Pablo Halpern )
Date: Wed, 16 Sep 1992 16:13:40 GMT
Raw View
In article <1992Sep12.174705.6381@taumet.com>, steve@taumet.com (Steve Clamage) writes:
|> inline T foo(int i) { return foo((long)i); } // foo(long) returns type T
|>
|> (Hmmm.  T foo(long) sounds like a beverage.)
|>
|> Make this NON-virtual in the base class, and don't override it in
|> any derived class.  You will then always get the correct version
|> of a virtual foo(long) every time you call foo with a (signed)
|> integer type, with no extra overhead.

I'm afraid not.  This is one of the biggest complaints I have with the
"clarification" done to the rules of disambiguating functions calls.  As it
stands now, defining a function in a derived class hides *all* functions of
the same name in its base classes.  This means that if you override any
foo() you must override all foo()s:

#include <iostream.h>

class A
{
public:
  virtual void foo(long i) { cout << "A::foo(long) " << i << endl; }
  virtual void foo(double d) { cout << "A::foo(double) " << d << endl; }
  void foo(int i) { foo((long) i); }
};

class B : public A
{
public:
  // Override foo(long) and foo(double)
  virtual void foo(long i) { cout << "B::foo(long) " << i << endl; }
  virtual void foo(double d) { cout << "B::foo(double) " << d << endl; }
  // Did not override foo(int)
};

int main(int, char**)
{
  A a;
  B b;

  a.foo(3);   // Okay, calls A::foo(int) which calls A::foo(long)
  b.foo(3);   // Ambiguous, B::foo(long) or B::foo(double).
              // Doesn't see A::foo(int)
}

This is more than a nuisance, it breaks inheritence and polymorpism.
Changing my example slightly:

class B : public A
{
public:
  // Override only foo(double)
  virtual void foo(double d) { cout << "B::foo(double) " << d << endl; }
  // Did not override foo(int) and foo(long)
};

int main(int, char**)
{
  A *pa;
  B b, *pb;

  pa = &b;
  pb = &b;

  // pa and pb point to the same object, but have different behavior
  pa->foo(3);   // Okay, calls A::foo(int) which calls A::foo(long)
  pb->foo(3);   // Calls B::foo(double). Doesn't see A::foo(int) or A::foo(long)
}


Of course this can be fixed by overriding all of the functions named foo()
in all derived classes of A.  Also, the compiler (cfrunt 2.1) DOES warn me
about B::foo() hiding A::foo().  This is already near the reasonable pain
threshold.  But what if I want to add a new version of foo() to class A?
Then I have to add a foo() with the same signature to every derived class
of A.  This is beyond the reasonable pain threshold.  The language is
broken.  Functions with different signatures should be considered different
functions and should not hide one another, even if their names are the
same.

--

- Pablo

------------------------------------------------------------------------
Pablo Halpern             (617) 290-3542
HP Waltham                pabloh@hpwarq.wal.hp.com

I am self-employed, so my opinions *do* reflect those of my employer.
However, they may not reflect the opinions of my client.
------------------------------------------------------------------------




Author: jason@jarthur.claremont.edu (Jason Merrill)
Date: Fri, 11 Sep 1992 17:12:15 GMT
Raw View
Why is the conversion int -> long (or unsigned int -> unsigned long) not
considered an integral promotion?

I am in the situation of trying to take either a pair of longs (with
default arguments) or a double for creation of a Fraction.  If I do
something like

Fraction(1),

my compiler (BC++ 3.0) complains about ambiguity.  This seems wrong to me.

--
Jason Merrill
jason@jarthur.claremont.edu