comp.lang.ada
 help / color / mirror / Atom feed
* Merits of re-dispatching [LONG]
@ 2002-02-07 10:26 Dmitry A. Kazakov
  2002-02-07 15:03 ` Hyman Rosen
                   ` (4 more replies)
  0 siblings, 5 replies; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-07 10:26 UTC (permalink / raw)


Hi!

C++ and Ada 95 support re-dispatching, which I think is inherently
unsafe. Consider the following:

type A is new Ada.Finalization.Limited_Controlled with ...
procedure Foo  (Object : A);
procedure Finalize (Object : in out A) ;

Now let AA derived from A override Foo and Finalize:

type AA is new A with record
   ... -- Some components, which may use components of A
end record;
procedure Foo  (Object : AA) ;
procedure Finalize (Object : in out AA) is
begin
   ... -- Finalize components, maybe accessing components of A
   Finalize (A (Object)); -- Finalize parent
end Finalize;

Consider the following implementation of A.Finalize:

procedure Finalize (Object : in out A) is
begin
   Foo (A'Class (Object)); -- Re-dispatch to the child's Foo
end Finalize;

When A is finalized as a part of AA, it re-dispatches to AA.Foo, which
may access already finalized parts of AA! A user of A has no way to
learn the problem from the specification of A. He must look into the
implementation of A.Finalize. The same is valid for Initialize as
well. Actually it is a common error among fresh baked C++ programmers
to call virtual functions (dispatch) from constructors.

Though merits of re-dispatching seem to me suspicious, it is easy to
implement it without casting. Let we really want object's IDENTITY,
i.e. objects which always "know" their actual type. Ada 95 perfectly
supports this without any casting!

package Self_Identifying is
   type A is new
      Ada.Finalization.Limited_Controlled with private;
   type A_Ptr is access all A'Class;
   procedure Finalize (Object : in out A);
   procedure Foo (Object : in out A);
private
   type A is new
      Ada.Finalization.Limited_Controlled with
   record
      Self : A_Ptr := A'Unchecked_Access; -- Class wide self-reference
   end record;
end Self_Identifying;

The implementation of Finalize:

   procedure Finalize (Object : in out A) is
   begin
      Foo (Object.Self.all); -- Dispatch to child's Foo
   end Finalize;

Now, if

   type AA is new A with null record;
   procedure Foo (Object : in out AA);

Then an object of AA will dispatch to AA.Foo during finalization
exactly like in the first example. The difference is that
self-identification can be explicitly exposed and documented:

   function Identity (Object : A['Class]) return A_Ptr is
   begin
      return Object.Self;
   end Identity;

With re-dispatching the contract of Finalize is not clear. According
to its specification it receives an argument of type A. Thus there
should be no legal way for Finalize to discover that actually an
instance of AA was passed. I.e. Foo (A'Class (Object)) should always
statically dispatch to A.Foo. Yet it dispatches to AA.Foo. This is a
violation of substitutability. [Not that I hold LSP for a sacral cow,
but there is no reason to encourage its violation.] I think that there
should be no exceptions from the rule "Want dispatching in a
subprogram? Make the argument class wide."

What is worse is that implementation of re-dispatching requires that
the type tag be accessible for not only class wide objects but also
for specific ones. Thus tagged types are reference types, type tag
shall be a part of a tagged object. As a consequence elementary types
cannot be tagged and so they are excluded from OOP. Maybe I have
missed something, but I see no other reason why not to allow for all
types inheritance, dispatching subroutines and class wide objects.

Methodically re-dispatching breaks segregation of class wide and
specific types returning us to the languages like C++, where there is
no difference between them. So Ada lingers somewhere in-between.

Is there examples where re-dispatching is really unavoidable? 

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-07 10:26 Merits of re-dispatching [LONG] Dmitry A. Kazakov
@ 2002-02-07 15:03 ` Hyman Rosen
  2002-02-08  1:29   ` Matthew Heaney
  2002-02-07 23:40 ` Nick Roberts
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 36+ messages in thread
From: Hyman Rosen @ 2002-02-07 15:03 UTC (permalink / raw)


"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message news:3c62524f.93369796@News.CIS.DFN.DE...
> C++ and Ada 95 support re-dispatching, which I think is inherently unsafe.

I don't know about Ada, but there is nothing unsafe about C++'s "redispatching".

> Consider the following:
>
> type A is new Ada.Finalization.Limited_Controlled with ...
> procedure Foo  (Object : A);
> procedure Finalize (Object : in out A) ;
>
> Now let AA derived from A override Foo and Finalize:
>
> type AA is new A with record
>    ... -- Some components, which may use components of A
> end record;
> procedure Foo  (Object : AA) ;
> procedure Finalize (Object : in out AA) is
> begin
>    ... -- Finalize components, maybe accessing components of A
>    Finalize (A (Object)); -- Finalize parent
> end Finalize;

In C++, Foo(Object:AA) is *not* an overrider of Foo(Object:A),
just another function that happens to share the same name. If you
want to override Foo in AA, the parameter type must remain A.

> Consider the following implementation of A.Finalize:
>
> procedure Finalize (Object : in out A) is
> begin
>    Foo (A'Class (Object)); -- Re-dispatch to the child's Foo
> end Finalize;

Again, this is false for C++. In a C++ constructor or destructor, the
type of the object is the type of the class of which the constructor or
destructor is a member. So in the C++ equivalent of Finalize(A), the
call to Foo(A'Class) would call Foo(A), not Foo(AA), because in
the destructor of A, the object is an A, not an AA, even when we
are destructing the A component of an AA. This is good, since, as
you point out, the AA part is no longer there!

> Actually it is a common error among fresh baked C++ programmers
> to call virtual functions (dispatch) from constructors.

True, but the error is that (from their point of view) the wrong function
gets called, not that the right function gets called on destructed pieces
of objects.

I guess this is another entry for my "why C++ is better than Ada" file :-)
(Or maybe it's the first? :-)






^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-07 10:26 Merits of re-dispatching [LONG] Dmitry A. Kazakov
  2002-02-07 15:03 ` Hyman Rosen
@ 2002-02-07 23:40 ` Nick Roberts
  2002-02-08  8:56   ` Dmitry A. Kazakov
  2002-02-08  1:06 ` Matthew Heaney
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 36+ messages in thread
From: Nick Roberts @ 2002-02-07 23:40 UTC (permalink / raw)


On Thu, 07 Feb 2002 10:26:00 GMT, dmitry@elros.cbb-automation.de (Dmitry A.
Kazakov) wrote:

>Hi!
>
>C++ and Ada 95 support re-dispatching, which I think is inherently
>unsafe. Consider the following:
>
>type A is new Ada.Finalization.Limited_Controlled with ...
>procedure Foo  (Object : A);
>procedure Finalize (Object : in out A) ;
>
>Now let AA derived from A override Foo and Finalize:
>
>type AA is new A with record
>   ... -- Some components, which may use components of A
>end record;

What do you mean "which may use components of A"?

-- 
Nick Roberts

-- 
Nick Roberts



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-07 10:26 Merits of re-dispatching [LONG] Dmitry A. Kazakov
  2002-02-07 15:03 ` Hyman Rosen
  2002-02-07 23:40 ` Nick Roberts
@ 2002-02-08  1:06 ` Matthew Heaney
  2002-02-08  9:48   ` Dmitry A. Kazakov
  2002-02-08 18:10   ` Hyman Rosen
  2002-02-08 18:33 ` Nick Roberts
  2002-02-14 20:57 ` Tucker Taft
  4 siblings, 2 replies; 36+ messages in thread
From: Matthew Heaney @ 2002-02-08  1:06 UTC (permalink / raw)



"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message
news:3c62524f.93369796@News.CIS.DFN.DE...
> procedure Finalize (Object : in out A) is
> begin
>    Foo (A'Class (Object)); -- Re-dispatch to the child's Foo
> end Finalize;
>
 > When A is finalized as a part of AA, it re-dispatches to AA.Foo, which
> may access already finalized parts of AA! A user of A has no way to
> learn the problem from the specification of A. He must look into the
> implementation of A.Finalize. The same is valid for Initialize as
> well. Actually it is a common error among fresh baked C++ programmers
> to call virtual functions (dispatch) from constructors.

Yes, but in Ada95, operations are static by default.  You have to take
active steps in order to force the dispatching to occur.  So the scenario
you describe cannot happen accidently.

This is not the case in C++, where virtual member functions called through a
reference or pointer automatically dispatch.  So yes you can very easily
have the problem you cite in C++.  (Which is why many C++ books point out
this feature of the language.)


> Though merits of re-dispatching seem to me suspicious, it is easy to
> implement it without casting. Let we really want object's IDENTITY,
> i.e. objects which always "know" their actual type. Ada 95 perfectly
> supports this without any casting!
>
> package Self_Identifying is
>    type A is new
>       Ada.Finalization.Limited_Controlled with private;
>    type A_Ptr is access all A'Class;
>    procedure Finalize (Object : in out A);
>    procedure Foo (Object : in out A);
> private
>    type A is new
>       Ada.Finalization.Limited_Controlled with
>    record
>       Self : A_Ptr := A'Unchecked_Access; -- Class wide self-reference
>    end record;
> end Self_Identifying;

This is a horrible way to do this.  Don't use a named access type when an
access discriminant will do:

  type Handle_Type (O : access A'Class) is limited null record;

  type A is new Limited_Controlled with record
    H : Handle_Type (A'Access);
  end record;


> Is there examples where re-dispatching is really unavoidable?

There is a very simple solution to your problem: don't do it.






^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-07 15:03 ` Hyman Rosen
@ 2002-02-08  1:29   ` Matthew Heaney
  2002-02-08  9:16     ` Dmitry A. Kazakov
  0 siblings, 1 reply; 36+ messages in thread
From: Matthew Heaney @ 2002-02-08  1:29 UTC (permalink / raw)



"Hyman Rosen" <hyrosen@mail.com> wrote in message
news:1013094178.985786@master.nyc.kbcfp.com...
> Again, this is false for C++. In a C++ constructor or destructor, the
> type of the object is the type of the class of which the constructor or
> destructor is a member. So in the C++ equivalent of Finalize(A), the
> call to Foo(A'Class) would call Foo(A), not Foo(AA), because in
> the destructor of A, the object is an A, not an AA, even when we
> are destructing the A component of an AA. This is good, since, as
> you point out, the AA part is no longer there!

Yes, that's true, but it's easy to think that the virtual member function
call will dispatch, because that's how it works for every other virtual
member function:

class B
{
public:
   virtual ~B();
   virtual void f();
   virtual void g();
};

void B::f()
{
   g();  //dispatches
}

B::~B()
{
   g();  //does not dispatch
}

You have to learn that the call to g() doesn't dispatch in the dtor.  This
behavior is certainly not immediately obvious, especially for those new to
the language.

You seemed to have missed the point about this error-prone feature of C++: a
naive programmer is going to depend on dispatching to occur.  He has to
learn --the hard way-- that the dtor is special.

> True, but the error is that (from their point of view) the wrong function
> gets called, not that the right function gets called on destructed pieces
> of objects.

But the point is that it is an error, that is very easy to make.


> I guess this is another entry for my "why C++ is better than Ada" file :-)

Again, you seemed to have missed the point.  The scenario Dmitry described
cannot happed by accident in Ada95: the programmer must have taken special
action for it to occur.  This is not the case in C++.

(Note that I like C++, and I use it every day.  But by any measure Ada95 is
a much safer language than C++.)






^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-07 23:40 ` Nick Roberts
@ 2002-02-08  8:56   ` Dmitry A. Kazakov
  0 siblings, 0 replies; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-08  8:56 UTC (permalink / raw)


On Thu, 07 Feb 2002 23:40:42 GMT, nickroberts@ukf.net (Nick Roberts)
wrote:

>On Thu, 07 Feb 2002 10:26:00 GMT, dmitry@elros.cbb-automation.de (Dmitry A.
>Kazakov) wrote:
>
>>Hi!
>>
>>C++ and Ada 95 support re-dispatching, which I think is inherently
>>unsafe. Consider the following:
>>
>>type A is new Ada.Finalization.Limited_Controlled with ...
>>procedure Foo  (Object : A);
>>procedure Finalize (Object : in out A) ;
>>
>>Now let AA derived from A override Foo and Finalize:
>>
>>type AA is new A with record
>>   ... -- Some components, which may use components of A
>>end record;
>
>What do you mean "which may use components of A"?

It was a bad wording , I admit. Should be "some components, which may
depend on components of A".

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08  1:29   ` Matthew Heaney
@ 2002-02-08  9:16     ` Dmitry A. Kazakov
  2002-02-08 18:30       ` Hyman Rosen
  2002-02-08 23:51       ` Merits of re-dispatching [LONG] Matthew Heaney
  0 siblings, 2 replies; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-08  9:16 UTC (permalink / raw)


On Thu, 7 Feb 2002 20:29:42 -0500, "Matthew Heaney" <mheaney@on2.com>
wrote:

>"Hyman Rosen" <hyrosen@mail.com> wrote in message
>news:1013094178.985786@master.nyc.kbcfp.com...
>> Again, this is false for C++. In a C++ constructor or destructor, the
>> type of the object is the type of the class of which the constructor or
>> destructor is a member. So in the C++ equivalent of Finalize(A), the
>> call to Foo(A'Class) would call Foo(A), not Foo(AA), because in
>> the destructor of A, the object is an A, not an AA, even when we
>> are destructing the A component of an AA. This is good, since, as
>> you point out, the AA part is no longer there!
>
>Yes, that's true, but it's easy to think that the virtual member function
>call will dispatch, because that's how it works for every other virtual
>member function:
>
>class B
>{
>public:
>   virtual ~B();
>   virtual void f();
>   virtual void g();
>};
>
>void B::f()
>{
>   g();  //dispatches
>}
>
>B::~B()
>{
>   g();  //does not dispatch
>}
>
>You have to learn that the call to g() doesn't dispatch in the dtor.  This
>behavior is certainly not immediately obvious, especially for those new to
>the language.
>
>You seemed to have missed the point about this error-prone feature of C++: a
>naive programmer is going to depend on dispatching to occur.  He has to
>learn --the hard way-- that the dtor is special.

Yes, but there are two things here.

First C++ does not make any difference between class wide and specific
objects. The same type B is a class wide in B::f () and specific in
B::~B (). So the difference in how B::f and B::~B are dealing with
calls to g(). This is IMO bad.

Second. The behaviour of B::~B is safe. One should never expect
dispatching in a specific subroutine like destructor. My point is that
the language should prohibit dispatching from specific subroutines.
Therefore in my opinion the behaviour of B::f is unsafe. It is
declared as virtual (dispatching) which means that inside B::f the
type is already *known*. Thus there is no need in [re-]dispatching. A
call to g() can be statically resolved.

The reason why C++ dispatches in B::f (), is that otherwise it would
impossible to have class wide subroutines [see the point 1]. In
contrary to this Ada 95 does have class wide routines, so my question,
why [explicit] re-dispatching is supported in Ada 95?

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08  1:06 ` Matthew Heaney
@ 2002-02-08  9:48   ` Dmitry A. Kazakov
  2002-02-09  0:16     ` Matthew Heaney
  2002-02-08 18:10   ` Hyman Rosen
  1 sibling, 1 reply; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-08  9:48 UTC (permalink / raw)


On Thu, 7 Feb 2002 20:06:42 -0500, "Matthew Heaney" <mheaney@on2.com>
wrote:

>"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message
>news:3c62524f.93369796@News.CIS.DFN.DE...
>>
>> package Self_Identifying is
>>    type A is new
>>       Ada.Finalization.Limited_Controlled with private;
>>    type A_Ptr is access all A'Class;
>>    procedure Finalize (Object : in out A);
>>    procedure Foo (Object : in out A);
>> private
>>    type A is new
>>       Ada.Finalization.Limited_Controlled with
>>    record
>>       Self : A_Ptr := A'Unchecked_Access; -- Class wide self-reference
>>    end record;
>> end Self_Identifying;
>
>This is a horrible way to do this.  Don't use a named access type when an
>access discriminant will do:
>
>  type Handle_Type (O : access A'Class) is limited null record;
>
>  type A is new Limited_Controlled with record
>    H : Handle_Type (A'Access);
>  end record;

I like it, but it has its own drawbacks. If only the component H
should be exposed and all others be hidden, then one should make a lot
of equilibristic:

--
-- Public part of A
--
   type A_Public;
   type A_Handle (O : access A_Public'Class) is
      limited null record;
   type A_Public is abstract new
      Ada.Finalization.Limited_Controlled with
   record
      H : A_Handle (A_Public'Access);
   end record;
   procedure Foo (Object : in out A_Public) is abstract;
--
-- Full A
--
   type A is new A_Public with private;
   type A_Ptr is access all A'Class;
   procedure Foo (Object : in out A);

private
   type A is new A_Public with record
      ... -- private components;
   end record;

But this is another question of course.

>> Is there examples where re-dispatching is really unavoidable?
>
>There is a very simple solution to your problem: don't do it.

So there is no such examples! (:-))

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08  1:06 ` Matthew Heaney
  2002-02-08  9:48   ` Dmitry A. Kazakov
@ 2002-02-08 18:10   ` Hyman Rosen
  2002-02-09  0:41     ` Matthew Heaney
  1 sibling, 1 reply; 36+ messages in thread
From: Hyman Rosen @ 2002-02-08 18:10 UTC (permalink / raw)


"Matthew Heaney" <mheaney@on2.com> wrote in message news:u668rmhvhdub11@corp.supernews.com...
> Yes, but in Ada95, operations are static by default.  You have to take
> active steps in order to force the dispatching to occur.  So the scenario
> you describe cannot happen accidently.
>
> This is not the case in C++, where virtual member functions called through a
> reference or pointer automatically dispatch.  So yes you can very easily
> have the problem you cite in C++.  (Which is why many C++ books point out
> this feature of the language.)

I don't understand the argument you are making. The problem seems to me that
when you make a dispatching call in the destructor (automatically in C++ or by
request in Ada), C++ will take you to a safe function, whereas Ada will take
you to a function which can manipulate already finalized submembers.





^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08  9:16     ` Dmitry A. Kazakov
@ 2002-02-08 18:30       ` Hyman Rosen
  2002-02-09  0:10         ` Matthew Heaney
  2002-02-12  8:32         ` Dmitry A. Kazakov
  2002-02-08 23:51       ` Merits of re-dispatching [LONG] Matthew Heaney
  1 sibling, 2 replies; 36+ messages in thread
From: Hyman Rosen @ 2002-02-08 18:30 UTC (permalink / raw)


"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message news:3c6392e8.2400843@News.CIS.DFN.DE...
> First C++ does not make any difference between class wide and specific
> objects. The same type B is a class wide in B::f () and specific in
> B::~B (). So the difference in how B::f and B::~B are dealing with
> calls to g(). This is IMO bad.

This is an incorrect view of what is happening. The type is class-wide
in both B::f and B::~B. Calls to B::g are dispatching in both. It's just
that while B::~B is running, the actual type of the object is B, so any
dispatching happens according to B's virtual functions, even if the B
part happened to be part of a derived class. Here's an example:

struct A { virtual void foo() { cout << "A::foo\n"; }void call_foo() { foo(); } virtual ~A() { call_foo(); } };
struct B : A { void foo() { cout << "B::foo\n"; } ~B() { call_foo(); } }
struct C : B { void foo() { cout << "C::foo\n"; } ~C() { call_foo(); } };

When a C object is destructed, it will print "C::foo"., "B::foo", "A::foo".

> type is already *known*. Thus there is no need in [re-]dispatching. A
> call to g() can be statically resolved.

As in my example, not if  the object is passed to some other routine which
makes the dispatching call.






^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-07 10:26 Merits of re-dispatching [LONG] Dmitry A. Kazakov
                   ` (2 preceding siblings ...)
  2002-02-08  1:06 ` Matthew Heaney
@ 2002-02-08 18:33 ` Nick Roberts
  2002-02-09  4:07   ` Nick Roberts
  2002-02-12 10:13   ` Dmitry A. Kazakov
  2002-02-14 20:57 ` Tucker Taft
  4 siblings, 2 replies; 36+ messages in thread
From: Nick Roberts @ 2002-02-08 18:33 UTC (permalink / raw)


On Thu, 07 Feb 2002 10:26:00 GMT, dmitry@elros.cbb-automation.de (Dmitry A.
Kazakov) wrote:

>C++ and Ada 95 support re-dispatching, which I think is inherently
>unsafe. Consider the following:
>...
>procedure Finalize (Object : in out A) is
>begin
>   Foo (A'Class (Object)); -- Re-dispatch to the child's Foo
>end Finalize;

This is what I would certainly describe as a 'pathalogical' case. It is a
basic principle in Finalize (it should be bloody obvious, but all the same
it should be taught) that you normally assume any and all extensions to the
Object have already been finalised, and so you don't try something like the
above redispatching!

>Though merits of re-dispatching seem to me suspicious, 

I'd like you to expand on that statement, please!

> it is easy to
>implement it without casting. 
>...
>With re-dispatching the contract of Finalize is not clear. According
>to its specification it receives an argument of type A. Thus there
>should be no legal way for Finalize to discover that actually an
>instance of AA was passed. I.e. Foo (A'Class (Object)) should always
>statically dispatch to A.Foo. Yet it dispatches to AA.Foo. This is a
>violation of substitutability. [Not that I hold LSP for a sacral cow,
>but there is no reason to encourage its violation.]

If the implementor is going to do the above, it is his responsibility to
ensure it will work; otherwise he must not do it (that is the 'contract'
implicit in implementing Finalization). I would say a compiler should give
a strong warning about redispatching on Object in Finalization; maybe it
should be explicitly made illegal in the next language revision.

>I think that there
>should be no exceptions from the rule "Want dispatching in a
>subprogram? Make the argument class wide."

A nice idea, but it would be too restrictive. There are many genuine cases
where it is necessary to be able to dispatch on an object passed in as a
specific type (to a primitive operation of that type).

>What is worse is that implementation of re-dispatching requires that
>the type tag be accessible for not only class wide objects but also
>for specific ones. Thus tagged types are reference types, type tag
>shall be a part of a tagged object. As a consequence elementary types
>cannot be tagged and so they are excluded from OOP. Maybe I have
>missed something, but I see no other reason why not to allow for all
>types inheritance, dispatching subroutines and class wide objects.

Except that it is really very unlikely in practice to be particularly
useful. How many times, in real software, is a primary type not going to
have to be a record type anyway? Very few!

>Methodically re-dispatching breaks segregation of class wide and
>specific types returning us to the languages like C++, where there is
>no difference between them. So Ada lingers somewhere in-between.

To be honest, I think Ada lingers somewhere inbetween the equally onerous
extremes of the pig trough and the ivory tower. The fact that Ada is
pragmatic enough to permit a primitive operation with two or more
parameters of the same (tagged) type, and a primitive operation of a tagged
type with futher parameters of that type's class, is as commendable as the
fact that it eschews features, such as multiple inheritance, which may be
very sexy, but have serious practical difficulties.

>Is there examples where re-dispatching is really unavoidable? 

Consider a tagged type (class) that represents a 'window' in a GUI
subsystem. It is derived from a 'drawable' type (class), that has a
(virtual) primitive operation 'redraw', which is called (by the window
manager/executive) in order to make the object redraw itself, whenever
necessary.

Our 'window' type (class) is a kind of container: it can contain a number
of other drawables. Perhaps it also has a background image that it draws
behind its components. Now consider its implementation of 'redraw'. This
must surely be on the lines of: (a) draw the background image; (b) for each
component drawable, call its 'redraw' (with appropriate parameters).

In Ada, the calls to the 'redraw' of each component will be explicitly
dispatching calls. (In C++, they will be dispatched to implicitly.) The
point to understand is that one of those components could be of the
'window' type (representing a window-within-a-window presumably); thus the
primitive redraw operation of the window type ends up redispatching to the
redraw of another window object. And yes, this situation does threaten the
danger of an infinite loop (many programming situations do).

I think, Dmitry, you will find in practice many genuine cases where this
kind of redispatching is impractical to avoid.

All the best,

-- 
Nick Roberts



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08  9:16     ` Dmitry A. Kazakov
  2002-02-08 18:30       ` Hyman Rosen
@ 2002-02-08 23:51       ` Matthew Heaney
  2002-02-12  9:02         ` Dmitry A. Kazakov
  1 sibling, 1 reply; 36+ messages in thread
From: Matthew Heaney @ 2002-02-08 23:51 UTC (permalink / raw)



"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message
news:3c6392e8.2400843@News.CIS.DFN.DE...
> First C++ does not make any difference between class wide and specific
> objects. The same type B is a class wide in B::f () and specific in
> B::~B (). So the difference in how B::f and B::~B are dealing with
> calls to g(). This is IMO bad.

It's bad to the extent that it falsifies the programmer's mental model of
how he thinks it should work.  It violates the "principle of least
astonishment."

> Second. The behaviour of B::~B is safe. One should never expect
> dispatching in a specific subroutine like destructor. My point is that
> the language should prohibit dispatching from specific subroutines.
> Therefore in my opinion the behaviour of B::f is unsafe. It is
> declared as virtual (dispatching) which means that inside B::f the
> type is already *known*. Thus there is no need in [re-]dispatching. A
> call to g() can be statically resolved.

Well, the programmer can turn off the dispatching:

void B::f()
{
   B::g();  //no dispatching
}

Here C++ and Ada95 diverge wrt syntactic overhead: in Ada95 you pay to turn
dispatching on, and in C++ you pay to turn dispatching off.

 > The reason why C++ dispatches in B::f (), is that otherwise it would
> impossible to have class wide subroutines [see the point 1]. In
> contrary to this Ada 95 does have class wide routines, so my question,
> why [explicit] re-dispatching is supported in Ada 95?

Because you may want to let a derived type override the algorithm in the
parent type.

A class-wide operation typically declares an overall algorithm, and
dispatching is used to provide the details:

package P is
   type T is tagged limited null record;
   procedure Op1 (O : T);
   procedure Op2 (O : T);
   procedure Op (O : T'Class);
end P;

package body P is
   procedure Op (O : T'Class) is  --static
   begin
      if <something> then
         Op1 (O);  --dynamic
     else
         Op2 (O);  --dynamic
     end if;
   end Op;
...
end P;

This is fine, but if you want to change the algorithm used by class-wide
operation Op, you can't.

If you want to allow a derived type to replace the whole algorithm (not just
the parts done by Op1 and Op2), then you have to make the operation
primitive for the type, and then turn on the dispatching:

package body P is
   procedure Op (O : T) is
   begin
      if <something> then
         Op1 (T'Class (O));
      else
         Op2 (T'Class (O));
      end if;
   end Op;
...
end P;

Now a derived type (say, P.C.NT) can override Op.

In general, an operation --primitive or class-wide-- should document which
dispatching operations it calls, so that a derived type knows what is
expected of it.

This was the problem with your original example: you seemed to argue that it
was some kind of language flaw in that you could call a dispatching
operation in the Finalize operation.  But this isn't bad because of any flaw
in the language, this is bad because it's a dumb thing to do.

The idiom for implementing a primitive Finalize operation is to finalize the
specific type, and then call the Finalize operation of its parent.  In
particular, this means you shouldn't call any dispatching operations.  In
practice this isn't an issue, because operations are statically bound by
default.

If a Finalize operation does call dispatching operations, then of course
this is a programmer error, no different from any other kind of programmer
error.






^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08 18:30       ` Hyman Rosen
@ 2002-02-09  0:10         ` Matthew Heaney
  2002-02-12  8:32         ` Dmitry A. Kazakov
  1 sibling, 0 replies; 36+ messages in thread
From: Matthew Heaney @ 2002-02-09  0:10 UTC (permalink / raw)



"Hyman Rosen" <hyrosen@mail.com> wrote in message
news:1013192956.289787@master.nyc.kbcfp.com...
> This is an incorrect view of what is happening.

Yes, we all know this is an incorrect view.  But the programmer has to learn
that this view is not the correct view.  He learns it because something
doesn't work the way he expects.

You learn by forming a mental model of how something works, and then testing
that model via experience.  When something behaves in a way that is
inconsistent with your model, then your model has been falsified, and you
have to change it so that there are no more surprises (you can make accurate
predications).

The issue in C++ is that when programmers first learn that language, they
very naturally form a mental model that says, "when I call a virtual member
function (through a ref or ptr, which may be implicit), the call is
dynamically bound."  This model is very successful at making accurate
predications.

However, in a dtor, the language behaves in a way that is inconsistant with
this otherwise good model of reality.  Once you learn why, then the fact
that it does behave that way makes sense, but it does surprise programmers.

The whole issue of "affordance" in user interface design concerns exactly
this issue.  When you use a tool, you form a mental model what to do to make
a certain thing happen.  But when you use the tool, and it fails to do what
you expect, then you are naturally surprised.  Good user interfaces are
designed to that the user is never surprised.

Ada95 has a flaw here: predefined equality "reemerges" when the type is
passed as a generic formal type, and when it's the component of a composite
type.  If you've overriden predefined equality for a type, this is *never*
what you expect.  (You naturally assume that your overridden operator is
that one that will be called, but it isn't, and this is a surprise.  From a
human engineering point of view, this is a flaw.)







^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08  9:48   ` Dmitry A. Kazakov
@ 2002-02-09  0:16     ` Matthew Heaney
  0 siblings, 0 replies; 36+ messages in thread
From: Matthew Heaney @ 2002-02-09  0:16 UTC (permalink / raw)



"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message
news:3c6397e5.3677906@News.CIS.DFN.DE...
So there is no such examples! (:-))

As I pointed out in a previous message, you want the language to allow you
to do this (redispatching), in order to allow the derived type to override
the entire algorithm, instead of just parts of it.

But you should always advertise which operations dispatch in an operation
(especially if the operation is primitive for the type).  Calling a
dispatching operation from inside another method is not simply an
implementation detail.  (Unlike the case in which the call is static, which
really is an implemenation detail.)






^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08 18:10   ` Hyman Rosen
@ 2002-02-09  0:41     ` Matthew Heaney
  0 siblings, 0 replies; 36+ messages in thread
From: Matthew Heaney @ 2002-02-09  0:41 UTC (permalink / raw)



"Hyman Rosen" <hyrosen@mail.com> wrote in message
news:1013191766.895310@master.nyc.kbcfp.com...
> I don't understand the argument you are making. The problem seems to me
that
> when you make a dispatching call in the destructor (automatically in C++
or by
> request in Ada), C++ will take you to a safe function, whereas Ada will
take
> you to a function which can manipulate already finalized submembers.

First of all, the programmer may depend on the derived class member function
being called, so saying that it's "safe" to call the base class member
function instead of the derived class member function is specious.  Yes, we
all know why the language behaves this way, and we agree that the language
should behave this way, but that doesn't help the programmer who mistakenly
believes otherwise.  Even if the base class member function is called, his
class is still broken.

Second of all, you can't make a strict one-for-one comparison of the
languages wrt destruction (C++) and finalization (Ada95).  In C++, the
derived part has been destroyed, and who knows what mayhem would ensue if a
member function were called after that had happened.  So of course C++ does
the right thing by not calling the derived class member function.

However, in Ada95, the Finalize operation of the parent is called while the
Finalize operation of the derived type is still executing (per the idiom,
it's the last thing you do before returning).  So the derived part of the
type is still there.  To call a dispatching operation in the parent's
Finalize amounts to a contract violation (a precondition wasn't satisfied by
the caller -- here, the parent type).  This is bad, but it's no different
then any other contract violation.

And who knows?  You could easily implement the Finalize operation using
dispatching calls -- it's up to the designer of the class.  For example:

package P is
   type T is new Finalization.Limited_Controlled with null record;
   procedure Op1 (O : in out T);
   procedure Op2 (O : in out T);
   procedure Finalize (O : in out T);
end P;

package body P is
   procedure Finalize (O : in out T) is
   begin
      Op1 (T'Class (O));  --dispatch
      Op2 (T'Class (O));
   end;
end P;

package P.C is
   type NT is new T with null record;
   procedure Op1 (O : in out NT);
   procedure Op2 (O : in out NT);
   procedure Finalize (O : in out NT);
end P.C:

package body P.C is
   procedure Finalize (O : in out NT) is
   begin
      Finalize (T (O));  --call parent's version
   end;
end P.C;

Here the derived type implements Finalize by calling the parent's Finalize,
who then calls the derived type back.  This is perfectly legal, and
perfectly safe.  It's also idiosyncratic, but that's another matter.







^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08 18:33 ` Nick Roberts
@ 2002-02-09  4:07   ` Nick Roberts
  2002-02-12 10:13   ` Dmitry A. Kazakov
  1 sibling, 0 replies; 36+ messages in thread
From: Nick Roberts @ 2002-02-09  4:07 UTC (permalink / raw)


On Fri, 08 Feb 2002 18:33:31 GMT, nickroberts@ukf.net (Nick Roberts)
strongly typed:

>This is what I would certainly describe as a 'pathalogical' case. It is a

Although maybe I ought to spell it "pathological" ;-)

-- 
Nick Roberts



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08 18:30       ` Hyman Rosen
  2002-02-09  0:10         ` Matthew Heaney
@ 2002-02-12  8:32         ` Dmitry A. Kazakov
  2002-02-12 21:37           ` Hyman Rosen
  2002-02-13 19:58           ` Dave Harris
  1 sibling, 2 replies; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-12  8:32 UTC (permalink / raw)


On Fri, 8 Feb 2002 13:30:04 -0500, "Hyman Rosen" <hyrosen@mail.com>
wrote:

>"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message news:3c6392e8.2400843@News.CIS.DFN.DE...
>> First C++ does not make any difference between class wide and specific
>> objects. The same type B is a class wide in B::f () and specific in
>> B::~B (). So the difference in how B::f and B::~B are dealing with
>> calls to g(). This is IMO bad.
>
>This is an incorrect view of what is happening. The type is class-wide
>in both B::f and B::~B. Calls to B::g are dispatching in both. It's just
>that while B::~B is running, the actual type of the object is B, so any
>dispatching happens according to B's virtual functions, even if the B
>part happened to be part of a derived class.

That is one of several possible interpretation of what happens in C++
using Ada terms. However, I prefer mine, because it is consistent with
the fact that the type tag [= vtab] is constant, thus the actual
specific type is also constant. Like in Ada it is only a view
conversion.

>struct A { virtual void foo() { cout << "A::foo\n"; }void call_foo() { foo(); } virtual ~A() { call_foo(); } };
>struct B : A { void foo() { cout << "B::foo\n"; } ~B() { call_foo(); } }
>struct C : B { void foo() { cout << "C::foo\n"; } ~C() { call_foo(); } };
>
>When a C object is destructed, it will print "C::foo"., "B::foo", "A::foo".
>
>> type is already *known*. Thus there is no need in [re-]dispatching. A
>> call to g() can be statically resolved.
>
>As in my example, not if  the object is passed to some other routine which
>makes the dispatching call.

When you pass it to some other routine an implicit specific->class
wide conversion happens, so the routine can dispatch again. An Ada 95
equivalent would be:

procedure Y (A : in out Object'Class);
procedure X (A : in out Object) is
begin
   Y (A'Class (A));
end X;

Exactly this behaviour is IMO unsafe.

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08 23:51       ` Merits of re-dispatching [LONG] Matthew Heaney
@ 2002-02-12  9:02         ` Dmitry A. Kazakov
  0 siblings, 0 replies; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-12  9:02 UTC (permalink / raw)


On Fri, 8 Feb 2002 18:51:00 -0500, "Matthew Heaney" <mheaney@on2.com>
wrote:

>"Dmitry A. Kazakov" <dmitry@elros.cbb-automation.de> wrote in message
>news:3c6392e8.2400843@News.CIS.DFN.DE...

> > The reason why C++ dispatches in B::f (), is that otherwise it would
>> impossible to have class wide subroutines [see the point 1]. In
>> contrary to this Ada 95 does have class wide routines, so my question,
>> why [explicit] re-dispatching is supported in Ada 95?
>
>Because you may want to let a derived type override the algorithm in the
>parent type.
>
>A class-wide operation typically declares an overall algorithm, and
>dispatching is used to provide the details:
>
>package P is
>   type T is tagged limited null record;
>   procedure Op1 (O : T);
>   procedure Op2 (O : T);
>   procedure Op (O : T'Class);
>end P;
>
>package body P is
>   procedure Op (O : T'Class) is  --static
>   begin
>      if <something> then
>         Op1 (O);  --dynamic
>     else
>         Op2 (O);  --dynamic
>     end if;
>   end Op;
>...
>end P;
>
>This is fine, but if you want to change the algorithm used by class-wide
>operation Op, you can't.

And this is good, because a class-wide operation is not defined for
the root type. It is defined for the closure of all types derived from
the root. Thus it cannot be overriden. It is a progam design flaw if
you need to do so.

What you want is actually multiple implementations, but this is not
the realm of class-wided routines, but of dispatching ones. Consider
class-wide routines as an [good] alternative to generic routines. You
cannot override a generic routine, you can only overload it.

Purely theoretically it is thinkable to build a type system with one
additional type level: class-class-wide type. Then one could have an
ability to dispatch on class-wide routines:

procedure MetaOp (O : T'Class'Class) is
begin
   Op (O); -- Meta dispatch
end MetaOp;

Though I do not think that such thing will appear in Ada very soon
(:-))

>In general, an operation --primitive or class-wide-- should document which
>dispatching operations it calls, so that a derived type knows what is
>expected of it.

It is a maintenace disaster. Though LSP cannot be enforced, one should
support it as much as possible.

However, my point was that the language should not support
re-dispatching. If a programmer want it he/she must do some work, for
instance, add a type identification member. It is very similar to the
Unchecked_Conversion case.

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-08 18:33 ` Nick Roberts
  2002-02-09  4:07   ` Nick Roberts
@ 2002-02-12 10:13   ` Dmitry A. Kazakov
  1 sibling, 0 replies; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-12 10:13 UTC (permalink / raw)


On Fri, 08 Feb 2002 18:33:31 GMT, nickroberts@ukf.net (Nick Roberts)
wrote:

>On Thu, 07 Feb 2002 10:26:00 GMT, dmitry@elros.cbb-automation.de (Dmitry A.
>Kazakov) wrote:
>
>>C++ and Ada 95 support re-dispatching, which I think is inherently
>>unsafe. Consider the following:
>>...
>>procedure Finalize (Object : in out A) is
>>begin
>>   Foo (A'Class (Object)); -- Re-dispatch to the child's Foo
>>end Finalize;
>
>This is what I would certainly describe as a 'pathalogical' case. It is a
>basic principle in Finalize (it should be bloody obvious, but all the same
>it should be taught) that you normally assume any and all extensions to the
>Object have already been finalised, and so you don't try something like the
>above redispatching!
>
>>Though merits of re-dispatching seem to me suspicious, 
>
>I'd like you to expand on that statement, please!

I see no pressing necessity in supporting re-dispatching. At the same
time the price of its supporting is high.

>> it is easy to
>>implement it without casting. 
>>...
>>With re-dispatching the contract of Finalize is not clear. According
>>to its specification it receives an argument of type A. Thus there
>>should be no legal way for Finalize to discover that actually an
>>instance of AA was passed. I.e. Foo (A'Class (Object)) should always
>>statically dispatch to A.Foo. Yet it dispatches to AA.Foo. This is a
>>violation of substitutability. [Not that I hold LSP for a sacral cow,
>>but there is no reason to encourage its violation.]
>
>If the implementor is going to do the above, it is his responsibility to
>ensure it will work; otherwise he must not do it (that is the 'contract'
>implicit in implementing Finalization). I would say a compiler should give
>a strong warning about redispatching on Object in Finalization; maybe it
>should be explicitly made illegal in the next language revision.

This is the C++'s way. I would prefer a more radical aproach, i.e.
removing the language support of re-dispatching. It means that the
language will not require that the conversion class->specific is a
view conversion. Just remove this requirement and re-dispatching will
disappear.

>>I think that there
>>should be no exceptions from the rule "Want dispatching in a
>>subprogram? Make the argument class wide."
>
>A nice idea, but it would be too restrictive. There are many genuine cases
>where it is necessary to be able to dispatch on an object passed in as a
>specific type (to a primitive operation of that type).

But you still can do it explicitly by providing a type identification
member.

Then generally, I have a strong feeling [not a proof of course] that
all that genuine cases are in fact more or less design flaws.

>>What is worse is that implementation of re-dispatching requires that
>>the type tag be accessible for not only class wide objects but also
>>for specific ones. Thus tagged types are reference types, type tag
>>shall be a part of a tagged object. As a consequence elementary types
>>cannot be tagged and so they are excluded from OOP. Maybe I have
>>missed something, but I see no other reason why not to allow for all
>>types inheritance, dispatching subroutines and class wide objects.
>
>Except that it is really very unlikely in practice to be particularly
>useful. How many times, in real software, is a primary type not going to
>have to be a record type anyway? Very few!

1. It would simplify the language. More regularity is IMO better. And
yes, I would like to have class-wide task and protected object types
(:-))

2. Extensible enumeration types are good candidates for class-wide
programming.

3. Let I want some special overflow processing for a numeric type? I
just derive from it and then override +. I can do it even in Ada 83,
but I cannot write a class-wide routine which will work with both
types. So I must call the devil = generics. (:-))

2 .& 3. Consider class-wide routines as an alternative to generics. In
many cases I would definitely prefer Integer'Class to type Int range
<>;

4. Attributes like Integer'Image (x) should be overridable dispatching
primitive routines: Image (x : Integer).

5. Interface inheritance. Forget for a minute that in Ada
implementation is always inherited. Consider you are developing a type
MyString. String is an array in the sense that it *looks* like an
array. In other words it has the interface of an array. So you derive
your type from an array type, provide a private implementation [which
possibly would be a record type], but the interface is of an array. So
you have indexing, slices etc.

>>Methodically re-dispatching breaks segregation of class wide and
>>specific types returning us to the languages like C++, where there is
>>no difference between them. So Ada lingers somewhere in-between.
>
>To be honest, I think Ada lingers somewhere inbetween the equally onerous
>extremes of the pig trough and the ivory tower. The fact that Ada is
>pragmatic enough to permit a primitive operation with two or more
>parameters of the same (tagged) type, and a primitive operation of a tagged
>type with futher parameters of that type's class, is as commendable as the
>fact that it eschews features, such as multiple inheritance, which may be
>very sexy, but have serious practical difficulties.

Well, I was always for MI and MD! (:-))

>>Is there examples where re-dispatching is really unavoidable? 
>
>Consider a tagged type (class) that represents a 'window' in a GUI
>subsystem. It is derived from a 'drawable' type (class), that has a
>(virtual) primitive operation 'redraw', which is called (by the window
>manager/executive) in order to make the object redraw itself, whenever
>necessary.
>
>Our 'window' type (class) is a kind of container: it can contain a number
>of other drawables. Perhaps it also has a background image that it draws
>behind its components. Now consider its implementation of 'redraw'. This
>must surely be on the lines of: (a) draw the background image; (b) for each
>component drawable, call its 'redraw' (with appropriate parameters).

I would make "redraw" class-wide and represent all child windows by
class-wide pointers.

Another example, where re-dispatching appears is layered protocols.
You have dispatching abstract Get to read one byte, then in some
derived type you have dispatching Get to read the mantra flux
distribution. You must somehow dispatch from there to read your bytes.
Oops, that would be re-dispatching! However, in this case one could
decouple the low-level I/O primitives in some separate type Driver and
use it as an access discriminant.

>In Ada, the calls to the 'redraw' of each component will be explicitly
>dispatching calls. (In C++, they will be dispatched to implicitly.) The
>point to understand is that one of those components could be of the
>'window' type (representing a window-within-a-window presumably); thus the
>primitive redraw operation of the window type ends up redispatching to the
>redraw of another window object. And yes, this situation does threaten the
>danger of an infinite loop (many programming situations do).

>I think, Dmitry, you will find in practice many genuine cases where this
>kind of redispatching is impractical to avoid.

Of course this is a question of balance. It is not that re-dispatching
should be disallowed. There is no way to do it as long a class-wide
self-pointer can be put into the object. My point is that there is no
need to support re-dispatching at the language level [the requirement
that the conversion to A'Class when applied to the formal parameter
gives you the actual type) 

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-12  8:32         ` Dmitry A. Kazakov
@ 2002-02-12 21:37           ` Hyman Rosen
  2002-02-13  9:29             ` Dmitry A. Kazakov
  2002-02-13 19:58           ` Dave Harris
  1 sibling, 1 reply; 36+ messages in thread
From: Hyman Rosen @ 2002-02-12 21:37 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> That is one of several possible interpretation of what happens in C++
> using Ada terms. However, I prefer mine, because it is consistent with
> the fact that the type tag [= vtab] is constant, thus the actual
> specific type is also constant. Like in Ada it is only a view
> conversion.

It is not "one of several possible interpretation", it is what actually
happens in C++! For implementations which use vtables, the compiler
generates code to change the vtable pointer of the object as it runs
through its chains of destructors. (Presumably the compiler may detect
cases where it doesn't need to do this, but this is what happens in
principle.)




^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-12 21:37           ` Hyman Rosen
@ 2002-02-13  9:29             ` Dmitry A. Kazakov
  2002-02-13 14:32               ` Hyman Rosen
  0 siblings, 1 reply; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-13  9:29 UTC (permalink / raw)


On Tue, 12 Feb 2002 16:37:50 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> That is one of several possible interpretation of what happens in C++
>> using Ada terms. However, I prefer mine, because it is consistent with
>> the fact that the type tag [= vtab] is constant, thus the actual
>> specific type is also constant. Like in Ada it is only a view
>> conversion.
>
>It is not "one of several possible interpretation", it is what actually
>happens in C++! For implementations which use vtables, the compiler
>generates code to change the vtable pointer of the object as it runs
>through its chains of destructors. (Presumably the compiler may detect
>cases where it doesn't need to do this, but this is what happens in
>principle.)

Does the C++ standard require this? I do not know, but if yes, then
C++ is even "better" than I used to think. (:-))

I remember one C run-time library implementation, which had printf
modifying the format string and restoring it before return. It was a
GREAT idea. Unfortunately, the compiler allocated string literals in
the read-only memory. (:-))

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-13  9:29             ` Dmitry A. Kazakov
@ 2002-02-13 14:32               ` Hyman Rosen
  0 siblings, 0 replies; 36+ messages in thread
From: Hyman Rosen @ 2002-02-13 14:32 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> Does the C++ standard require this? I do not know, but if yes, then
> C++ is even "better" than I used to think. (:-))

The standard doesn't specify an implementation, of course, but yes,
it requires this behavior. Under the covers, this, combined with
multiple inheritance and a desire for efficiency in the most common
cases, makes the implementation of constructors and destructors
potentially very complicated. Fortunately, this just has to be
figured out once by the compiler vendor. To the user, everything
looks and acts just like they expect (assuming their expectations
are correct :-).

The gcc team has developed an ABI for C++ that discusses these issues.
See <http://www.codesourcery.com/cxx-abi/abi.html>. It has some relevance
to Ada since GNAT's tagged types are compatible with equivalent C++
classes.

> I remember one C run-time library implementation, which had printf
> modifying the format string and restoring it before return. It was a
> GREAT idea. Unfortunately, the compiler allocated string literals in
> the read-only memory. (:-))

C++, like Ada, integrates its libraries into the language specification,
so vendors are allowed to use whatever system knowledge or magic they
want to implement them. It helps if the library team and compiler team
talk to each other :-)




^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-12  8:32         ` Dmitry A. Kazakov
  2002-02-12 21:37           ` Hyman Rosen
@ 2002-02-13 19:58           ` Dave Harris
  2002-02-14 15:06             ` Dmitry A. Kazakov
  1 sibling, 1 reply; 36+ messages in thread
From: Dave Harris @ 2002-02-13 19:58 UTC (permalink / raw)


dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) wrote (abridged):
> That is one of several possible interpretation of what happens in C++
> using Ada terms. However, I prefer mine, because it is consistent with
> the fact that the type tag [= vtab] is constant, thus the actual
> specific type is also constant.
> [...]
> When you pass it to some other routine an implicit specific->class
> wide conversion happens, so the routine can dispatch again.

This doesn't work. If you look at the 3 calls to call_foo() in Hyman 
Rosen's example, you'll see it dispatches to 3 different places. Therefore 
we cannot view the object has having a constant type with 2 kinds of 
dispatching. A specific->class-wide conversion would have it printing 
C::foo 2 or 3 times. Your view doesn't match the visible behaviour.

If you think in terms of a conversion, the different calls to call_foo() 
must convert to different types. It's not enough to switch the vtable on 
and off, you need different conceptual vtables.

Given this, its simpler to think of the conversion as happening as soon as 
the constructor is entered, rather than when the constructor calls out.


> An Ada 95 equivalent would be:

Your example only shows 2 types, so doesn't capture the richness of 
behaviour of Hyman's example which has 3. I suspect you have misunderstood 
it.

  Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
      brangdon@cix.co.uk      |   And close your eyes with holy dread,
                              |  For he on honey dew hath fed
 http://www.bhresearch.co.uk/ |   And drunk the milk of Paradise."



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-13 19:58           ` Dave Harris
@ 2002-02-14 15:06             ` Dmitry A. Kazakov
  2002-02-16 12:10               ` Dave Harris
  0 siblings, 1 reply; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-14 15:06 UTC (permalink / raw)


On Wed, 13 Feb 2002 19:58 +0000 (GMT Standard Time),
brangdon@cix.co.uk (Dave Harris) wrote:

>dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) wrote (abridged):
>> That is one of several possible interpretation of what happens in C++
>> using Ada terms. However, I prefer mine, because it is consistent with
>> the fact that the type tag [= vtab] is constant, thus the actual
>> specific type is also constant.
>> [...]
>> When you pass it to some other routine an implicit specific->class
>> wide conversion happens, so the routine can dispatch again.
>
>This doesn't work. If you look at the 3 calls to call_foo() in Hyman 
>Rosen's example, you'll see it dispatches to 3 different places. Therefore 
>we cannot view the object has having a constant type with 2 kinds of 
>dispatching. A specific->class-wide conversion would have it printing 
>C::foo 2 or 3 times. Your view doesn't match the visible behaviour.
>
>If you think in terms of a conversion, the different calls to call_foo() 
>must convert to different types. It's not enough to switch the vtable on 
>and off, you need different conceptual vtables.

Yes. C has a tag, B in C has another and A in B in C has the third.
This is why I do not like embedded tags.

>Given this, its simpler to think of the conversion as happening as soon as 
>the constructor is entered, rather than when the constructor calls out.

Surely. There is a type conversion, but it is an "in-place"
conversion. It changes neither the object C nor its type. It only
shifts the reference to C.B.

>> An Ada 95 equivalent would be:
>
>Your example only shows 2 types, so doesn't capture the richness of 
>behaviour of Hyman's example which has 3. I suspect you have misunderstood 
>it.

That there are three types changes nothing. It works pretty well in
Ada 95:

type A is ...
procedure Foo (Object : A) is
begin
   Put_Line ("A.Foo");
end Foo;
procedure Finalize (Object : in out A) is
begin
   Foo (Object);            -- No dispatch here, A.Foo is called
end Finalize;
--------------------
type B is new A ...
procedure Foo (Object : B) is
begin
   Put_Line ("B.Foo");
end Foo;
procedure Finalize (Object : in out B) is
begin
   Foo (Object);            -- No dispatch here, B.Foo is called
   Finalize (A (Object)); -- C++ adds this automatically
end Finalize;
---------------------------
type C is new B...
procedure Foo (Object : C) is
begin
   Put_Line ("C.Foo");
end Foo;
procedure Finalize (Object : in out C) is
begin
   Foo (Object);            -- No dispatch here, C.Foo is called
   Finalize (B (Object)); -- C++ adds this automatically
end Finalize;

When C is finalized, then C.Foo, B.Foo, A.Foo will be printed.

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-07 10:26 Merits of re-dispatching [LONG] Dmitry A. Kazakov
                   ` (3 preceding siblings ...)
  2002-02-08 18:33 ` Nick Roberts
@ 2002-02-14 20:57 ` Tucker Taft
  2002-02-15 15:43   ` Dmitry A. Kazakov
  4 siblings, 1 reply; 36+ messages in thread
From: Tucker Taft @ 2002-02-14 20:57 UTC (permalink / raw)


"Dmitry A. Kazakov" wrote:
> 
> Hi!
> 
> C++ and Ada 95 support re-dispatching, which I think is inherently
> unsafe.
> ...

Redispatching is the default in most OO languages.  In some, like
Eiffel and Java, there is no alternative.  In C++, it is possible
to explicitly prevent redispatching by using the :: operator.  By default,
if you use a reference, a pointer (e.g. "this"), or just
the virtual function name by itself, you get (re)dispatching.

In Ada 95, we chose to make static binding the default, with (re)dispatching
happening only when the actual parameter is of the classwide type (T'Class).
This was a conscious decision, and means that places where (re)dispatching
is occurring are visible to the reader, and requires some explicit intent
by the programmer.  This reduces the cases of unintentional redispatching,
and (hopefully) encourages the programmer to mention internal redispatching
as part of the semantics of the operation.

In your example, you have used redispatching in a finalization routine.
As you point out, that is an unusual time to do so, and is probably
unwise, because the descendant's finalization activity has probably already
taken place.  Hence, your example illustrates an inappropriate use
of redispatching.  On the other hand, there are many examples of appropriate
use of redispatching.  However, these all are cases where the semantics
of the routine should be explicitly idenfitied as including redispatching.

For example, consider the following:

    type T is tagged ...
    function Image(X : T) return String;  -- Return string representation of T
    procedure Put(X : T);  -- Display T on standard output; by default,
                           -- put result of (redispatch to) Image function

In this example, we have defined the "default" semantics of "Put" to
be a Text_IO.Put of a redispatching call to Image.  This would allow a programmer
to override Image only, and have the desirable effect of having Put's results altered
as well.  Here is the implementation of this "default" Put:

   procedure Put(X : T) is -- by default, put result of calling Image
   begin
       Text_IO.Put(Image(T'Class(X));
   end Put;

On the other hand, for some complex types, it may be desirable to take advantage
of the fact we are displaying on standard output, and we might create a multi-line,
nicely indented version of the display.  In that case, we would want to override
Put to not simply put out the result of calling Image.

Clearly this relationship between the Image and Put primitive operations
needs to be explicit in the spec for "T".  Otherwise, when we release a new
version, we might choose to change Put to effectively "inline" the semantics
of Image, rather than making a redispatching call.  In that case, the
programmer could choose to inherit the "Put" semantics as is while changing "Image",
but could no longer override only Image and expect Put to change "correspondingly."

This is a fairly simple example, but with a complex type, the relationships
can get very complicated.  If no redispatching is done, then all of the
operations are independent "black boxes," and they can be inherited if they
already have the desired effect, or overridden if they don't.  When redispatching
is present, the programmer needs to know the relationships between the primitive
operations, so they can make appropriate decisions about inheriting versus
overriding.

As mentioned above, in Ada 95, we chose to make static binding the default for calls
from one primitive to the next, thereby providing minimal "coupling" between
the primitives, and allowing them to be reused as black boxes.  However, we
felt it was essential that we also supported explicit redispatching, for the
cases where "pass-the-buck" semantics were appropriate, where by default
one primitive should redispatch to another, allowing a fewer number of primitives to
be overridden when extending a type.

In any case, we agree that redispatching can be the wrong thing to do, and
making it the default opens the door for unintentional coupling between
primitives, effectively requiring that a programmer see the source code
for the primitives of a parent type to determine the effect of overriding
some but not all of the primitives in a derived type.

> ...
> Is there examples where re-dispatching is really unavoidable?

If you are always willing to override all primitive operations, 
then re-dispatching is avoidable.  However, if you want to override
only some of the primitives, and rely on the others being defined
by default in terms of these, you can simplify the job of extending
a type.  But as indicated above, we certainly agree that redispatching
is part of the *visible* semantics of a primitive operation, when extending
a type.
> 
> Regards,
> Dmitry Kazakov

-- 
-Tucker Taft   stt@avercom.net   http://www.avercom.net
Chief Technology Officer, AverCom Corporation (A Titan Company) 
Bedford, MA  USA (AverCom was formerly the Commercial Division of AverStar:
http://www.averstar.com/~stt)



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-14 20:57 ` Tucker Taft
@ 2002-02-15 15:43   ` Dmitry A. Kazakov
  0 siblings, 0 replies; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-15 15:43 UTC (permalink / raw)


On Thu, 14 Feb 2002 15:57:05 -0500, Tucker Taft <stt@avercom.net>
wrote:

>"Dmitry A. Kazakov" wrote:
>> 
>> Hi!
>> 
>> C++ and Ada 95 support re-dispatching, which I think is inherently
>> unsafe.
>> ...
>
>Redispatching is the default in most OO languages.  In some, like
>Eiffel and Java, there is no alternative.  In C++, it is possible
>to explicitly prevent redispatching by using the :: operator.  By default,
>if you use a reference, a pointer (e.g. "this"), or just
>the virtual function name by itself, you get (re)dispatching.
>
>In Ada 95, we chose to make static binding the default, with (re)dispatching
>happening only when the actual parameter is of the classwide type (T'Class).
>This was a conscious decision, and means that places where (re)dispatching
>is occurring are visible to the reader, and requires some explicit intent
>by the programmer.  This reduces the cases of unintentional redispatching,
>and (hopefully) encourages the programmer to mention internal redispatching
>as part of the semantics of the operation.

I consider class-wide types as the major advance in OO in recent
years, but it is another issue (:-)).

>In your example, you have used redispatching in a finalization routine.
>As you point out, that is an unusual time to do so, and is probably
>unwise, because the descendant's finalization activity has probably already
>taken place.  Hence, your example illustrates an inappropriate use
>of redispatching.  On the other hand, there are many examples of appropriate
>use of redispatching.  However, these all are cases where the semantics
>of the routine should be explicitly idenfitied as including redispatching.
>
>For example, consider the following:
>
>    type T is tagged ...
>    function Image(X : T) return String;  -- Return string representation of T
>    procedure Put(X : T);  -- Display T on standard output; by default,
>                           -- put result of (redispatch to) Image function
>
>In this example, we have defined the "default" semantics of "Put" to
>be a Text_IO.Put of a redispatching call to Image.  This would allow a programmer
>to override Image only, and have the desirable effect of having Put's results altered
>as well.  Here is the implementation of this "default" Put:
>
>   procedure Put(X : T) is -- by default, put result of calling Image
>   begin
>       Text_IO.Put(Image(T'Class(X));
>   end Put;
>
>On the other hand, for some complex types, it may be desirable to take advantage
>of the fact we are displaying on standard output, and we might create a multi-line,
>nicely indented version of the display.  In that case, we would want to override
>Put to not simply put out the result of calling Image.

This resembles the example of Nick Roberts. Of course, such small
examples have little to do with real systems, so it is had to judge.
But I think that here there are [should be (:-))] alternatives to
redispatching as well.

1. Methodically, if we are going to override Put, then we honestly
believe that the place where it displays its output, is a property of
the type. Thus it would be logical to have:

type T (Output : access Display'Class) is ...;

Display is some type representing the variety of output devices. then
Put could be made class-wide:

procedure Put (X : T'Class) is
   Text : constant String := Image (X); -- Dispatch on representation
begin
   Write (Output, Text); -- Dispatch on devices
end Put;

2. If it is not a property of the type, then Put could have an
additional parameter:

procedure Put (X : T'Class; Output: in out Display'Class) is
   Text : constant String := Image (X); -- Dispatch on representation
begin
   Write (Output, Text); -- Dispatch on devices
end Put;

Only if multiple implementations of Put cannot be factored out as a
set of types [an OO-fundamentalist would claim that it is always
possible, but I am not so sure], then we are in trouble. Anyway Ada 95
supports redispatching without implicit conversion to T'Class.

>Clearly this relationship between the Image and Put primitive operations
>needs to be explicit in the spec for "T".  Otherwise, when we release a new
>version, we might choose to change Put to effectively "inline" the semantics
>of Image, rather than making a redispatching call.  In that case, the
>programmer could choose to inherit the "Put" semantics as is while changing "Image",
>but could no longer override only Image and expect Put to change "correspondingly."
>
>This is a fairly simple example, but with a complex type, the relationships
>can get very complicated.  If no redispatching is done, then all of the
>operations are independent "black boxes," and they can be inherited if they
>already have the desired effect, or overridden if they don't.  When redispatching
>is present, the programmer needs to know the relationships between the primitive
>operations, so they can make appropriate decisions about inheriting versus
>overriding.
>
>As mentioned above, in Ada 95, we chose to make static binding the default for calls
>from one primitive to the next, thereby providing minimal "coupling" between
>the primitives, and allowing them to be reused as black boxes.

Amen! (:-))

>However, we
>felt it was essential that we also supported explicit redispatching, for the
>cases where "pass-the-buck" semantics were appropriate, where by default
>one primitive should redispatch to another, allowing a fewer number of primitives to
>be overridden when extending a type.
>
>In any case, we agree that redispatching can be the wrong thing to do, and
>making it the default opens the door for unintentional coupling between
>primitives, effectively requiring that a programmer see the source code
>for the primitives of a parent type to determine the effect of overriding
>some but not all of the primitives in a derived type.
>
>> ...
>> Is there examples where re-dispatching is really unavoidable?
>
>If you are always willing to override all primitive operations, 
>then re-dispatching is avoidable.  However, if you want to override
>only some of the primitives, and rely on the others being defined
>by default in terms of these, you can simplify the job of extending
>a type.  But as indicated above, we certainly agree that redispatching
>is part of the *visible* semantics of a primitive operation, when extending
>a type.

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-14 15:06             ` Dmitry A. Kazakov
@ 2002-02-16 12:10               ` Dave Harris
  2002-02-18  8:57                 ` Dmitry A. Kazakov
  0 siblings, 1 reply; 36+ messages in thread
From: Dave Harris @ 2002-02-16 12:10 UTC (permalink / raw)


dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) wrote (abridged):
> That there are three types changes nothing. It works pretty well in
> Ada 95:

That still doesn't capture the full complexity, because although you have 
Foo you don't have a Call_Foo. The call to Foo from within Call_Foo must 
be dispatching, because it must dispatch to different places. Given that 
it can do this, it's simplest to say the call to Foo from within the 
destructor is dispatching too, in the same way. So there is no 
non-dispatching call in the C++.

I don't know Ada, but I think it would look like:

 type A is ...
 procedure Foo (Object : A) is
 begin
    Put_Line ("A.Foo");
 end Foo;
 procedure Call_Foo (Object : A) is
 begin
    Foo( Object );  -- Dispatching, A.Foo or B.Foo called.
 end Foo;
 procedure Finalize (Object : in out A) is
 begin
    Call_Foo (Object);
    Foo (Object);
 end Finalize;

 type B is new A ...
 procedure Foo (Object : B) is
 begin
    Put_Line ("B.Foo");
 end Foo;
 procedure Finalize (Object : in out B) is
 begin
    Call_Foo (Object); 
    Foo( Object );
    Finalize (A (Object));
 end Finalize;

This should produce B.Foo B.Foo A.Foo A.Foo. The call to Foo from 
B.Finalize can use exactly the same implementation as the call to Foo from 
A.Call_Foo. Both end up in B.Foo when the object is of type B. Both are 
dispatching calls.

  Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
      brangdon@cix.co.uk      |   And close your eyes with holy dread,
                              |  For he on honey dew hath fed
 http://www.bhresearch.co.uk/ |   And drunk the milk of Paradise."



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching [LONG]
  2002-02-16 12:10               ` Dave Harris
@ 2002-02-18  8:57                 ` Dmitry A. Kazakov
  2002-02-18 19:47                   ` Merits of re-dispatching Dave Harris
  0 siblings, 1 reply; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-18  8:57 UTC (permalink / raw)


On Sat, 16 Feb 2002 12:10 +0000 (GMT Standard Time),
brangdon@cix.co.uk (Dave Harris) wrote:

>dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) wrote (abridged):
>> That there are three types changes nothing. It works pretty well in
>> Ada 95:
>
>That still doesn't capture the full complexity, because although you have 
>Foo you don't have a Call_Foo. The call to Foo from within Call_Foo must 
>be dispatching, because it must dispatch to different places. Given that 
>it can do this, it's simplest to say the call to Foo from within the 
>destructor is dispatching too, in the same way. So there is no 
>non-dispatching call in the C++.
>
>I don't know Ada, but I think it would look like:
>
> type A is ...
> procedure Foo (Object : A) is
> begin
>    Put_Line ("A.Foo");
> end Foo;
> procedure Call_Foo (Object : A) is
> begin
>    Foo( Object );  -- Dispatching, A.Foo or B.Foo called.

One correction, it should be:

Foo (A'Class (Object)); -- Re-dispatch to "native" Foo

> end Foo;
> procedure Finalize (Object : in out A) is
> begin
>    Call_Foo (Object);
>    Foo (Object);
> end Finalize;
>
> type B is new A ...
> procedure Foo (Object : B) is
> begin
>    Put_Line ("B.Foo");
> end Foo;
> procedure Finalize (Object : in out B) is
> begin
>    Call_Foo (Object); 
>    Foo( Object );
>    Finalize (A (Object));
> end Finalize;
>
>This should produce B.Foo B.Foo A.Foo A.Foo.

With my correction it will produce:

B.Foo B.Foo B.Foo A.Foo.
 \         \        \         \
  \         \        \         Foo from A.Finalize -> A.Foo
    \         \       Call_Foo form A.Finalize -> B.Foo (dispatch)
     \        Foo from B.Finalize -> B.Foo
       Call_Foo form B.Finalize -> B.Foo (dispatch)

Alternatively, you could make Call_Foo class wide:

procedure Call_Foo (Object : A'Class) is
begin
    Foo (Object);  -- Dispatching
end Call_Foo;

This is the official way to say that we want to dispatch on A from it
and thus it is defined for all A derivates. [=>it cannot be
overridden] Then a call to Call_Foo form B.Finalize could look as
follows:

   Call_Foo (A'Class (Object)); -- Prepare Object to re-dispatching

The result output will be same. [Ada is a consistent language (:-))]

>The call to Foo from B.Finalize can use exactly the same implementation
>as the call to Foo from A.Call_Foo.

If both are dispatching, but they are not. Only the call from Call_Foo
is dispatching.

>Both end up in B.Foo when the object is of type B. Both are 
>dispatching calls.

That's the point. Why on earth a call to Foo from B.Finalize should
dispatch? The type is known, so there is only one version of Foo to be
called. It can be resolved at compile time and even inlined if
necessary. In Ada it does not dispatch. And that is good. But my point
was that additionally to that there must be no way to enforce
dispatching [like in Call_Foo], because it is unsafe and inefficient.

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching
  2002-02-18  8:57                 ` Dmitry A. Kazakov
@ 2002-02-18 19:47                   ` Dave Harris
  2002-02-19  9:20                     ` Dmitry A. Kazakov
  0 siblings, 1 reply; 36+ messages in thread
From: Dave Harris @ 2002-02-18 19:47 UTC (permalink / raw)


dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) wrote (abridged):
> > This should produce B.Foo B.Foo A.Foo A.Foo.
> 
> With my correction it will produce:
> 
> B.Foo B.Foo B.Foo A.Foo.

Right. So you see that Ada is different from C++, and your description of 
C++ in Ada terms gives the wrong results. You said earlier that:

    That is one of several possible interpretation of what happens
    in C++ using Ada terms.

but the other interpretation you offered doesn't correspond to how C++ 
behaves.

That was my only point, really. 


> Why on earth a call to Foo from B.Finalize should dispatch? The type
> is known, so there is only one version of Foo to be called.

Let's not muddle the issue with compiler optimisations. A C++ compiler is 
allowed to replace dynamic dispatch with static if it can deduce the 
dynamic type of the object at compile-time. Constructors are not a special 
case (although the deduction is unusually simple for them). All of this is 
under the "as if" rule. C++ must behave as if it dynamically dispatched in 
constructors.

  Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
      brangdon@cix.co.uk      |   And close your eyes with holy dread,
                              |  For he on honey dew hath fed
 http://www.bhresearch.co.uk/ |   And drunk the milk of Paradise."



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching
  2002-02-18 19:47                   ` Merits of re-dispatching Dave Harris
@ 2002-02-19  9:20                     ` Dmitry A. Kazakov
  2002-02-21  5:49                       ` Hyman Rosen
  0 siblings, 1 reply; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-19  9:20 UTC (permalink / raw)


On Mon, 18 Feb 2002 19:47 +0000 (GMT Standard Time),
brangdon@cix.co.uk (Dave Harris) wrote:

>dmitry@elros.cbb-automation.de (Dmitry A. Kazakov) wrote (abridged):
>> > This should produce B.Foo B.Foo A.Foo A.Foo.
>> 
>> With my correction it will produce:
>> 
>> B.Foo B.Foo B.Foo A.Foo.
>
>Right. So you see that Ada is different from C++, and your description of 
>C++ in Ada terms gives the wrong results. You said earlier that:
>
>    That is one of several possible interpretation of what happens
>    in C++ using Ada terms.
>
>but the other interpretation you offered doesn't correspond to how C++ 
>behaves.

My interpretation was that if C++ dispatches then it treats the type
as class wide,, otherwise as specific. Then the  object is always
treated as specific in all calls from destructor. So what? Maybe it is
hard to simulate, but well possible to explain.

>That was my only point, really. 
>
>> Why on earth a call to Foo from B.Finalize should dispatch? The type
>> is known, so there is only one version of Foo to be called.
>
>Let's not muddle the issue with compiler optimisations. A C++ compiler is 
>allowed to replace dynamic dispatch with static if it can deduce the 
>dynamic type of the object at compile-time.

Which is almost impossible in general case. However it has nothing to
do with optimisation. It is the language design issue.

>Constructors are not a special 
>case (although the deduction is unusually simple for them). All of this is 
>under the "as if" rule. C++ must behave as if it dynamically dispatched in 
>constructors.

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching
  2002-02-19  9:20                     ` Dmitry A. Kazakov
@ 2002-02-21  5:49                       ` Hyman Rosen
  2002-02-21  9:04                         ` Dmitry A. Kazakov
  0 siblings, 1 reply; 36+ messages in thread
From: Hyman Rosen @ 2002-02-21  5:49 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> My interpretation was that if C++ dispatches then it treats the type
> as class wide,, otherwise as specific. Then the  object is always
> treated as specific in all calls from destructor. So what? Maybe it is
> hard to simulate, but well possible to explain.

I don't know what it is you think that C++ does, but in fact, all calls
to virtual methods in C++ are dispatching, unless you ask for a specifc
version using the pointer->CLASS::FUNCTION() notation. In constructors
and destructors, the type of the object is the type whose constructor
or destructor is running, and that is the type used for dispatching.

If the compiler can deduce the actual type of an object in a dispatching
call, it is of course free to call the proper method directly, and it
may be the case that such deduction is easier in a constructor or
destructor, but that is completely irrelevant to understanding what is
happening from the language viewpoint.

You may persist in maintaining your incorrect mental model of what is
going on, but the only effect will be to lead you astray in complicated
caes.




^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching
  2002-02-21  5:49                       ` Hyman Rosen
@ 2002-02-21  9:04                         ` Dmitry A. Kazakov
  2002-02-21 18:17                           ` Hyman Rosen
  0 siblings, 1 reply; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-21  9:04 UTC (permalink / raw)


On Thu, 21 Feb 2002 05:49:10 GMT, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> My interpretation was that if C++ dispatches then it treats the type
>> as class wide,, otherwise as specific. Then the  object is always
>> treated as specific in all calls from destructor. So what? Maybe it is
>> hard to simulate, but well possible to explain.
>
>I don't know what it is you think that C++ does, but in fact, all calls
>to virtual methods in C++ are dispatching, unless you ask for a specifc
>version using the pointer->CLASS::FUNCTION() notation. In constructors
>and destructors, the type of the object is the type whose constructor
>or destructor is running, and that is the type used for dispatching.

OK, it dispatches as if it didn't. Therefore I say that it does not.
What happens internally is of no interest.

>If the compiler can deduce the actual type of an object in a dispatching
>call, it is of course free to call the proper method directly, and it
>may be the case that such deduction is easier in a constructor or
>destructor, but that is completely irrelevant to understanding what is
>happening from the language viewpoint.

The viewpoint of C++ language, but I am not talking about C++
definition of "dispatch" and its implementation issues.

>You may persist in maintaining your incorrect mental model of what is
>going on, but the only effect will be to lead you astray in complicated
>caes.

I still prefer a model where types of objects are not arbitrarily
changed [no matter physically or mentally] depending on what kind of
method is used.

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching
  2002-02-21  9:04                         ` Dmitry A. Kazakov
@ 2002-02-21 18:17                           ` Hyman Rosen
  2002-02-22  9:21                             ` Dmitry A. Kazakov
  0 siblings, 1 reply; 36+ messages in thread
From: Hyman Rosen @ 2002-02-21 18:17 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> OK, it dispatches as if it didn't. Therefore I say that it does not.

Well, you can say anything you like, but it certainly does dispatch
using the type of the constructor or destructor which is running.

> What happens internally is of no interest.

If that is so, then stop saying that it does not dispatch, because
that is true *only* internally.

> The viewpoint of C++ language, but I am not talking about C++
> definition of "dispatch" and its implementation issues.

You began this discussion by claiming that the equivalent of
redispatching from Finalize was unsafe in C++, and I have been
correcting your mistaken impressions about C++ ever since.
This has nothing whatever to do with implementations of C++,
only the definition of its behavior according to its standard.

> I still prefer a model where types of objects are not arbitrarily
> changed [no matter physically or mentally] depending on what kind of
> method is used.

You may prefer this, but that is not the way C++ works. It is the
way Ada works, which leads to the problem you originally noticed,
of redispatching to code which manipulates already Finalized members.







^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching
  2002-02-21 18:17                           ` Hyman Rosen
@ 2002-02-22  9:21                             ` Dmitry A. Kazakov
  2002-02-22 16:59                               ` Hyman Rosen
  0 siblings, 1 reply; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-22  9:21 UTC (permalink / raw)


On Thu, 21 Feb 2002 13:17:48 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> OK, it dispatches as if it didn't. Therefore I say that it does not.
>
>Well, you can say anything you like, but it certainly does dispatch
>using the type of the constructor or destructor which is running.
>
>> What happens internally is of no interest.
>
>If that is so, then stop saying that it does not dispatch, because
>that is true *only* internally.

That's the whole point. Dispatching vs. not dispatching is not an
implementation detail, but a part of the contract. It seems that we
are using different definitions of dispatch, thus we'll never agree.

>> The viewpoint of C++ language, but I am not talking about C++
>> definition of "dispatch" and its implementation issues.
>
>You began this discussion by claiming that the equivalent of
>redispatching from Finalize was unsafe in C++,

No. I said exactly the opposite:

"The behaviour of B::~B is safe."

I.e. that C++ behaviour is safe in destructors, because it does not
<dispatch> [= dispatches as if it didn't].

>and I have been
>correcting your mistaken impressions about C++ ever since.
>This has nothing whatever to do with implementations of C++,
>only the definition of its behavior according to its standard.
>
>> I still prefer a model where types of objects are not arbitrarily
>> changed [no matter physically or mentally] depending on what kind of
>> method is used.
>
>You may prefer this, but that is not the way C++ works. It is the
>way Ada works, which leads to the problem you originally noticed,
>of redispatching to code which manipulates already Finalized members.

The single problem with Ada approach is that its application was in my
view not enough puristic and consequent as it could be. Namely, no
re-dispatching should be allowed at all. I definitely do not want that
self-contradictory mixture of types and their closures as in C++.

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching
  2002-02-22  9:21                             ` Dmitry A. Kazakov
@ 2002-02-22 16:59                               ` Hyman Rosen
  2002-02-25  8:51                                 ` Dmitry A. Kazakov
  0 siblings, 1 reply; 36+ messages in thread
From: Hyman Rosen @ 2002-02-22 16:59 UTC (permalink / raw)


Dmitry A. Kazakov wrote:
> That's the whole point. Dispatching vs. not dispatching is not an
> implementation detail, but a part of the contract. It seems that we
> are using different definitions of dispatch, thus we'll never agree.

Yes, it's part of the contract, and in C++ it always dispatches.
There is only one definition of dispatch that I'm aware of - when
a virtual method is called through a pointer to an object, the method
actually called is determined by the actual type of the object, not
the declared type of the pointer. In C++, it is this actual type which
changes as control progresses through the hierarchy of constructors
or destructors.

> No. I said exactly the opposite:
> "The behaviour of B::~B is safe."

Here is what you said in your original message:
	C++ and Ada 95 support re-dispatching,
	which I think is inherently unsafe

> I definitely do not want that self-contradictory mixture
 > of types and their closures as in C++.

I don't even know what this means. C++ does not have
"self-contradictory" types.




^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: Merits of re-dispatching
  2002-02-22 16:59                               ` Hyman Rosen
@ 2002-02-25  8:51                                 ` Dmitry A. Kazakov
  0 siblings, 0 replies; 36+ messages in thread
From: Dmitry A. Kazakov @ 2002-02-25  8:51 UTC (permalink / raw)


On Fri, 22 Feb 2002 11:59:46 -0500, Hyman Rosen <hyrosen@mail.com>
wrote:

>Dmitry A. Kazakov wrote:
>> That's the whole point. Dispatching vs. not dispatching is not an
>> implementation detail, but a part of the contract. It seems that we
>> are using different definitions of dispatch, thus we'll never agree.
>
>Yes, it's part of the contract, and in C++ it always dispatches.
>There is only one definition of dispatch that I'm aware of - when
>a virtual method is called through a pointer to an object, the method
>actually called is determined by the actual type of the object, not
>the declared type of the pointer.

Compare: the actual type is class wide [no matter pointers, reference,
value], the parameter is dispatching.

>In C++, it is this actual type which
>changes as control progresses through the hierarchy of constructors
>or destructors.
>
>> No. I said exactly the opposite:
>> "The behaviour of B::~B is safe."
>
>Here is what you said in your original message:
>	C++ and Ada 95 support re-dispatching,
>	which I think is inherently unsafe

Yes this is may point that re-dispatching is unsafe. Also my point is
that destructors in C++ are safe because [in your terminology] they
dispatche as if the actual type were the formal type. This behaviour
is safe, because [according to my terminology] they do not dispatch at
all. Which IMO should be always so in a specific subroutine
[destructors cannot be class wide].

>> I definitely do not want that self-contradictory mixture
> > of types and their closures as in C++.
>
>I don't even know what this means. C++ does not have
>"self-contradictory" types.

This means, I quote you :

"... actual type which changes as control progresses through the
hierarchy of constructors or destructors."

If there is no distinction between specific and class wide types, then
somersaults like morphing types are inevitable. One cannot mix sets
and its elements without a punishment.

Regards,
Dmitry Kazakov



^ permalink raw reply	[flat|nested] 36+ messages in thread

end of thread, other threads:[~2002-02-25  8:51 UTC | newest]

Thread overview: 36+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2002-02-07 10:26 Merits of re-dispatching [LONG] Dmitry A. Kazakov
2002-02-07 15:03 ` Hyman Rosen
2002-02-08  1:29   ` Matthew Heaney
2002-02-08  9:16     ` Dmitry A. Kazakov
2002-02-08 18:30       ` Hyman Rosen
2002-02-09  0:10         ` Matthew Heaney
2002-02-12  8:32         ` Dmitry A. Kazakov
2002-02-12 21:37           ` Hyman Rosen
2002-02-13  9:29             ` Dmitry A. Kazakov
2002-02-13 14:32               ` Hyman Rosen
2002-02-13 19:58           ` Dave Harris
2002-02-14 15:06             ` Dmitry A. Kazakov
2002-02-16 12:10               ` Dave Harris
2002-02-18  8:57                 ` Dmitry A. Kazakov
2002-02-18 19:47                   ` Merits of re-dispatching Dave Harris
2002-02-19  9:20                     ` Dmitry A. Kazakov
2002-02-21  5:49                       ` Hyman Rosen
2002-02-21  9:04                         ` Dmitry A. Kazakov
2002-02-21 18:17                           ` Hyman Rosen
2002-02-22  9:21                             ` Dmitry A. Kazakov
2002-02-22 16:59                               ` Hyman Rosen
2002-02-25  8:51                                 ` Dmitry A. Kazakov
2002-02-08 23:51       ` Merits of re-dispatching [LONG] Matthew Heaney
2002-02-12  9:02         ` Dmitry A. Kazakov
2002-02-07 23:40 ` Nick Roberts
2002-02-08  8:56   ` Dmitry A. Kazakov
2002-02-08  1:06 ` Matthew Heaney
2002-02-08  9:48   ` Dmitry A. Kazakov
2002-02-09  0:16     ` Matthew Heaney
2002-02-08 18:10   ` Hyman Rosen
2002-02-09  0:41     ` Matthew Heaney
2002-02-08 18:33 ` Nick Roberts
2002-02-09  4:07   ` Nick Roberts
2002-02-12 10:13   ` Dmitry A. Kazakov
2002-02-14 20:57 ` Tucker Taft
2002-02-15 15:43   ` Dmitry A. Kazakov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox