Thread overview
Assignment and down-casting
Mar 07, 2009
Yigal Chripun
Mar 07, 2009
Tim M
Mar 08, 2009
Tim M
Mar 08, 2009
Yigal Chripun
March 07, 2009
I just read this article by Dennis Richie posted on the NG [1], and I wanted to discuss a way to solve the const return issue.

here's some (general) code:

class A {}
class B : A {}

A func (A a) {
  .. do stuff ..
  return a;
}

void main() {
  B b = new B;
  A a1 = func(b);
  B a2 = cast(B) func(b); // will not compile with explicit cast
}

the explicit downcast could be removed if the compiler inserted the down-cast implicitly - assigning null or throwing if the down-cast fails.

this can be applied to constancy since const is super type of both immutable and mutable. I don't know how a const pointer/reference is implemented in D so assume that pointers to const objects have a certain bit set. (constancy runtime type)

// create a const object - bit is set
const T* cptr = cast(const T*) new Struct;
// create mutable object - bit unset
T* ptr = new Struct; // bit is unset

on assingment the bit is preserved and cannot be unset:
T* a = ptr; // bit unset
T* b = cptr; // fails - bit cannot be unset
const T* c = cptr; // bit is set
const T* d = ptr;  // bit remains unset

the second case fails by either assinging null to be or by throwing an exception.

invariant can use a different bit.

let's say we have:

const T foo(const T a, U b); // foo returns a

const T cptr = ...; //const object
      T ptr  = ...; // mutable object

T res = foo (cptr, something); // fails
T res = foo (ptr, something); // *works*
const T res = foo (cptr, something); // bit set for res
const T res = foo (ptr, something); // bit unset for res

just as before, the first foo fails by evaluating to null or by exception and the second foo works since the bit was never set so there's no problem "downcasting" to a regular, mutable, pointer/ref.

What do you think?

[1] http://www.lysator.liu.se/c/dmr-on-noalias.html
March 07, 2009
On Sun, 08 Mar 2009 06:59:04 +1300, Yigal Chripun <yigal100@gmail.com> wrote:

> I just read this article by Dennis Richie posted on the NG [1], and I wanted to discuss a way to solve the const return issue.
>
> here's some (general) code:
>
> class A {}
> class B : A {}
>
> A func (A a) {
>    .. do stuff ..
>    return a;
> }
>
> void main() {
>    B b = new B;
>    A a1 = func(b);
>    B a2 = cast(B) func(b); // will not compile with explicit cast
> }
>
> the explicit downcast could be removed if the compiler inserted the down-cast implicitly - assigning null or throwing if the down-cast fails.
>
> this can be applied to constancy since const is super type of both immutable and mutable. I don't know how a const pointer/reference is implemented in D so assume that pointers to const objects have a certain bit set. (constancy runtime type)
>
> // create a const object - bit is set
> const T* cptr = cast(const T*) new Struct;
> // create mutable object - bit unset
> T* ptr = new Struct; // bit is unset
>
> on assingment the bit is preserved and cannot be unset:
> T* a = ptr; // bit unset
> T* b = cptr; // fails - bit cannot be unset
> const T* c = cptr; // bit is set
> const T* d = ptr;  // bit remains unset
>
> the second case fails by either assinging null to be or by throwing an exception.
>
> invariant can use a different bit.
>
> let's say we have:
>
> const T foo(const T a, U b); // foo returns a


Thats bcos it's not a down cast. It's an up cast.
>
> const T cptr = ...; //const object
>        T ptr  = ...; // mutable object
>
> T res = foo (cptr, something); // fails
> T res = foo (ptr, something); // *works*
> const T res = foo (cptr, something); // bit set for res
> const T res = foo (ptr, something); // bit unset for res
>
> just as before, the first foo fails by evaluating to null or by exception and the second foo works since the bit was never set so there's no problem "downcasting" to a regular, mutable, pointer/ref.
>
> What do you think?
>
> [1] http://www.lysator.liu.se/c/dmr-on-noalias.html

March 08, 2009
What I was trying to say is that func needs to be called with an "A" but calling it with a "B" works because a "B" is a kind of "A". The function looses an information that it is a "B" returns it as an "A" which should be cast back to a "B" explicitly. This is corrct behavior as not all "A"s can be of kind "B".

One way to get the kind of behaviour you are looking for is to have the caller infer the type from the function and also have the function templated to work with any type that inherits from A. I have shown this in the following example with the extra step of not explicitly instantiating the template as that can also be inferred:

module temp;

import std.stdio;

class A
{
      void foo()
      {
            writefln("A.foo()");
      }
}

class B : A
{
      override void foo()
      {
            writefln("B.foo()");
      }
      void bar()
      {
            writefln("B.bar()");
      }
}

T func(T : A)(T a)
{
      //.. do stuff ..
      return a;
}

void main()
{
      auto thing = func(new B()); //same as auto thing = func!(B)(new B());
      thing.foo();
      thing.bar();

}


I'm not completly sure of how const/invariant work with object references so I won't attempt to answer that.
March 08, 2009
Tim M wrote:
> What I was trying to say is that func needs to be called with an "A" but
> calling it with a "B" works because a "B" is a kind of "A". The function
> looses an information that it is a "B" returns it as an "A" which should
> be cast back to a "B" explicitly. This is corrct behavior as not all
> "A"s can be of kind "B".
>
> One way to get the kind of behaviour you are looking for is to have the
> caller infer the type from the function and also have the function
> templated to work with any type that inherits from A. I have shown this
> in the following example with the extra step of not explicitly
> instantiating the template as that can also be inferred:
>
> module temp;
>
> import std.stdio;
>
> class A
> {
> void foo()
> {
> writefln("A.foo()");
> }
> }
>
> class B : A
> {
> override void foo()
> {
> writefln("B.foo()");
> }
> void bar()
> {
> writefln("B.bar()");
> }
> }
>
> T func(T : A)(T a)
> {
> //.. do stuff ..
> return a;
> }
>
> void main()
> {
> auto thing = func(new B()); //same as auto thing = func!(B)(new B());
> thing.foo();
> thing.bar();
>
> }
>
>
> I'm not completly sure of how const/invariant work with object
> references so I won't attempt to answer that.

I think you missed my point.
I used an example with classes as a generalization.
remember that in D we have:
        const(T)
          /\
         /  \
        /    \
       T    invariant(T)

basically I want to do:

ref const(T) max(T)(ref const(T) a, ref const(T) b){ return (a>b)?a:b; }

int a = 4;
int b = 5;
int res = max(a, b); //works due to "downcasting" from const(int) to int

this can be limited to constancy only if it doesn't make sense for classes.