February 22, 2005

xs0 wrote:
> That's impossible (really, cast is not overload-selection-operator, it's casting-operator). Any other suggestions?

If we had overloading on return type, then in some
situations we'd want some way to choose which return
type to use. Using cast for this would seem natural.

Of course, changing the semantics of cast may, for
all I know, raise other issues.
February 22, 2005
On Tue, 22 Feb 2005 10:20:33 +0100, xs0 <xs0@xs0.com> wrote:
>>> Well, I didn't specify which floor() and sqrt() get called, so one could  argue that the example was valid? :)
>>  If you had specified them, would it have compiled?
>> If so, post the example here, if not...
>
> What kind of questions is that? Are you suggesting you're not sure whether "return floor(sqrt(a))" can ever be a valid function body?

No.

> And I can't produce a compilable example, because the example included an overload on return type, which is not possible...

No, your example didn't show one, you described one being added later. All I want is an example that compiles. Which should be possible.

>> I notice you don't try examples which are posted, I think you should,  before commenting on them, it's a waste of time guessing what's going to  happen.
>
> Like I said, I can't produce a compilable example (which I thought would be somewhat obvious from the nature of this thread, but I guess it isn't).

See above.

>> True, however I'm also specifying the overload I want, because the  compiler cannot read my mind and I doesn't guess (thank Bob).
>
> Maybe, but you're specifying the overload you want merely as a side-effect of casting parameters.

No.

If the compiler errors because it cannot pick the overload, then, by adding a cast I am selecting an overload, the fact that it casts/converts is the side effect. In other words the point/purpose of the cast changes depending on what it's used for.

I think that, because the cast comes first, you're assuming it's the purpose, because the overload selection comes second it's a side effect. A side effect can occur first, just as the purpose can occur second.

>>> Casting a return value, otoh, happens _after_ the function returns, so  it can't be used for the same purpose...
>>  I disagree. As I said, I'm explicitly specifying the overload I mean.
>
> No, you're not.. You're just casting the return type. You can cast the return type now, so casting obviously doesn't mean you're trying to select an overload..

Yes, you can cast a return type now, now the purpose is to cast. Were it possible to cast to select an overload, then doing so would be the purpose.

The purpose of cast now, is not always to convert, but sometimes to select an overload, the purpose changes depending on where it's used and why (by definition).

>> So you're saying you have:
>>  int func(int a) {}
>> double func(int a) {}
>>  void main() {
>>   int a;
>>   a = func();
>> }
>>  and you want to call the double version, right?
>> If so, then yes, your syntax looks fine.
>
> Since you were so smart about compilable examples, I'm surprised you wrote an example that can't be compiled :P (Sorry, couldn't resist)

I find your comment petty.

The example above cannot be compiled because it is an error, I was giving an example of an error case which your syntax would solve.

The same is not true of your example, which was an example of 'before' the collision was added.

> I think you're not clear on how evaluation of expressions works, and that is the reason you think using cast for this would be fine. Let me try to explain.

I am not the only one, see Georg's reply.

> Let's focus just on ints and doubles. When you type cast(int)expression it's basically the same as if you called some compiler-provided function named, for example, cast_to_int(), so the above translates to cast_to_int(expression). That function has several overloads, in this case two (because I limited the example to two types):
>
> int cast_to_int(int value) {
>     return value;
> }
> int cast_to_int(double value) {
>     return ...; // the largest integer not less than value
> }
>
> Same goes for cast(double). Now, when you type
>
> f(cast(int)a, cast(double)b)
>
> it gets translated to
>
> f(cast_to_int(a), cast_to_double(b))
>
> and NOT to
>
> f(a, b); // OY, COMPILER, I'M TALKING TO YOU! I WANT f(int,double)
>
> So, before the func() gets the parameters, they're already cast to the right types. Incidentally, that makes an exact match with one of the overloads of func(), so the compiler knows which one you meant.
>
> OTOH, if you write cast(int)func(a,b), it gets translated to
>
> cast_to_int(func(a,b))
>
>  From that it's obvious that casting is done _after_ the function is evaluated, so casting can't have an effect on which function is selected. Got it? (now don't start arguing I made this up; this is how it actually works (I wrote a parser or two in my life, so I know this))

I understand everything you've said above, and I agree it works as you've said.

But, the point you're missing is that the 'intent' of a statement is not necessarily the same as the effect generated by the compiler.

In this case using a cast to "select an overload" is the intent of the programmer. The effect is that the "variable is converted", but, that is a side effect.

This behaviour/method is generally accepted and understood by programmers, and extending it to include return values will be the same, generally accepted and understood.

Further it requires no additional syntax and is easily parsable.

>> As I said above, when casting to cause an exact match I am explicitly  specifying the overload I want to use, because the compiler cannot read my  mind and doesn't guess.
>
> No, you're not specifying the overload, you're just casting the arguments,

This is not my 'intent' it is simply the effect.

> possibly producing an exact overload match..

This is the intent.

>> The other is converting to an int, sure, the cast does this, but, when  used to select an overload the intent is different, the fact that it's  converting (which it might not be, consider int->long) is secondary.
>
> no, it isn't secondary, just the opposite (and casting int to long is converting, the first is 32 bits, the other is 64 bits).

It's not converting per-se, more extending and setting bits to 0.

> the only cast that doesn't do anything is actually casting a type to the same type..

int -> uint makes no changes to the memory.

> (even if two types are the same length and casting doesn't change one bit, it will change the semantics of expressions; for example, if you cast int 3 to uint 3, you'll get exactly the memory contents; however, if you add 3000000000, you'll get a negative value in the first case and a positive value in the second case)

Sure, but as you and I have said, it makes no change to the memory.

Regardless, this is beside the point, except to say that if a programmer uses a cast to select an overload they need to consider the effect the conversion will have, but, again, the conversion is secondary to the intent which is to select the overload.

>>> The only reasonable case I can think of are templates, so you can use  the same code both for classes that are something, and classes than can  cast themselves as that same something (like a List or whatever). And in  that respect, having multiple possible casts might be a good idea..
>>   Classes can be implicitly cast to their parent types, I believe.
>> Is this what you're referring to? eg.
>
> Nope, I'm referring to something like this:
>
> class SomeClass(T) {
>     void doSomething(T param) {
>         List a=cast(List)param;
>
>         // now do something with a
>     }
> }
>
> This template will currently work for both Ts that are themselves Lists, and Ts that have opCast() that returns a List. But, opCast() can't be overloaded, and if you want it for something else, you have a problem..

Ok.

>>> Well, what is your better suggestion then?
>>  cast(type)
>
> That's impossible (really, cast is not overload-selection-operator, it's casting-operator). Any other suggestions?

No, this is the best method/soln I've heard so far.

>>> cast(T: int)    { return Something!(T).convert(this); }
>>> cast(T: double) { return Something!(T).convert(this); }
>>  That syntax does not seem obvious to me.
>
> Why not?

Well.. it looks like a function, returning nothing, taking something called T which must be an int, if that's the case why not just go:

cast(int) { .. }

in which case it looks just like the c++ hack to me.

So in short your example seems to be the c++ hack, with extra characters making it less clear (to me).

> How would you suggest overloading opCast() if return-type overloading is not allowed

No idea, I'll think about this, if, return type overloading is officially "not going to happen, ever".

> (which is the case this suggestion is trying to cover, and

Ahh, then we're talking at cross puposes. I am suggesting overload on return values, using a cast. I think it's the best solution. If denied that, then I'll try and think of another solution.

> I was just trying to show how T may not always be dummy)?

I dont think it's any different to using a dummy, and using typeof(param) on it.

>> Which is why I prefer cast(type), it has an obvious commonly understood  meaning.
>
> Yes, I agree completely, it's just not what you understand it to be...

It appears Georg agrees with me.

Regan
February 22, 2005
> 
> If the compiler errors because it cannot pick the overload, then, by  adding a cast I am selecting an overload, the fact that it casts/converts  is the side effect. In other words the point/purpose of the cast changes  depending on what it's used for.
> 

I haven't read the whole thread, but I think I get the general gist. IMHO, you have just shot yourself in the foot with "In other words the point/purpose of the cast changes depending on what it's used for"  I think that D's philosophy is to be clear and simple where possible, having situations where the behaviour of cast changes would be against that philosophy.
Also, what happens in the situation where:
int foo(int i) {};	<--- I really want this one called
char foo (int i) {};

char c = cast(char)(cast(int)foo(10));

Ugly, no?

Brad
February 22, 2005
On Tue, 22 Feb 2005 10:23:16 +0200, Georg Wrede <georg.wrede@nospam.org> wrote:
> Regan Heath wrote:
>> On Tue, 22 Feb 2005 01:26:44 +0100, xs0 <xs0@xs0.com> wrote:
>>
>>>> This whole issue comes down to where you draw the line, where you  balance  convenience vs pedanticism. (if that aint a word it should  be). Walter has  drawn it in one place, almost everyone disagrees in  some way, large or  small.
>
> True. But then we might not always be able to see the real reason.
> After all, his CV looks better than ours!

Well, in most cases I tend to agree with Walter, but, it's not due to his CV.

> Also, some of the reasons
> might take a lot of explaining, especially if they're subtle. All
> that is time away from actually writing the D compiler.   .-)

True, and we don't want that. :0)

> And sometimes it may be just as simple as a feature demanding more
> research and writing from Walter, than what it's worth.

True, which is why it's up to us to attempt to show how useful it can be, then he has some idea whether it's worth looking at.

>>> I certainly don't disagree with Walter; actually I think D is the best  language I've ever seen (at least in the compiled-to-native-code  category).
>
> Me too! And an ever increasing number of my friends here!

I think most people here, are here for this very reason. Or they think D will become the best and want to follow it's evolution.

>>> Well, considering how opCast is the only problem where not having  return-type-overloads is really an issue, perhaps it could just get  special treatment and the problem'd be solved?
>
> FTR: I still have no opinion for or against overloading on return
> type.

Fair enough.

> Can we find examples where not having it produces cumbersome code
> compared with having RTO?

Lets use opCast as an example. I think writing a class which you want to be convertable to several different basic types is a case where this would be useful, for example...

class SomeNewType {
  int opCast() {}
  float opCast() {}
}

void main() {
  SomeNewType p = new SomeNewType();
  int a;
  float b;

  a = p;  //error cannot implicitly cast from SomeNewType to int
  b = p;  //error cannot implicitly cast from SomeNewType to float
  a = cast(int)p;    //ok
  b = cast(float)p;  //ok
}

to achieve this, now, you need to use a seperate function for each i.e.

class SomeNewType {
  int toInt() {}
  float toFloat() {}
}

Not bad per-se. But, it leaves one wondering what the point of opCast is. Maybe the rationale is that you should only need 1 opCast, as in you cast to x, and all the other types you need should be castable from that type? If so, then why does int cast to more than 1 type? surely these cases are comparable?

Lets consider it when writing a template, i.e. generic programming.

template foo(T) {
  void foo(T p) {
    int a = cast(int)p;  //would be possible
    int a = p.toInt();   //ok
  }
}

but lets go further and try...

template foo(T, NT) {
  void foo(T p, out NT q) {
    q = cast(typeof(NT))p;  //would be possible
    q = p.to???();          //impossible?
  }
}

Granted, it seems reasonably rare for most programs to need to do this, there may be workarounds?

Another possible use is optimisation, it may be possible for your class to return types x, y and z more efficiently than returning x and having something else convert from x to y or x to z. i.e. maybe you're caching those values. or simply producing them from scratch is faster than converting from an existing value of another type.

Overall I think this is a minor feature, and maybe it's more work than it's worth. But then, maybe someone can come up with a really good use for it. I've done my best to lay it all out for everyone as I see it.

Regan
February 22, 2005
On Wed, 23 Feb 2005 10:41:05 +1300, <brad@domain.invalid> wrote:
>>  If the compiler errors because it cannot pick the overload, then, by  adding a cast I am selecting an overload, the fact that it casts/converts  is the side effect. In other words the point/purpose of the cast changes  depending on what it's used for.
>>
>
> I haven't read the whole thread, but I think I get the general gist. IMHO, you have just shot yourself in the foot with "In other words the point/purpose of the cast changes depending on what it's used for"  I think that D's philosophy is to be clear and simple where possible, having situations where the behaviour of cast changes would be against that philosophy.

No, you missunderstand. The behaviour isn't changing at all. Cast still does what cast does, the intent of the programmer is what changes.

> Also, what happens in the situation where:
> int foo(int i) {};	<--- I really want this one called
> char foo (int i) {};
>
> char c = cast(char)(cast(int)foo(10));
>
> Ugly, no?

Perhaps, matter of opinion.

Your example seems engineered to look bad however. Why do you have a "char foo" function, yet want to use the "int foo" one, to get a char? It makes little or no sense to me.

Regan
February 22, 2005
On Wed, 23 Feb 2005 11:36:27 +1300, Regan Heath wrote:

> On Tue, 22 Feb 2005 10:23:16 +0200, Georg Wrede <georg.wrede@nospam.org> wrote:
>> Regan Heath wrote:
>>> On Tue, 22 Feb 2005 01:26:44 +0100, xs0 <xs0@xs0.com> wrote:
>>>
>>>>> This whole issue comes down to where you draw the line, where you balance  convenience vs pedanticism. (if that aint a word it should be). Walter has  drawn it in one place, almost everyone disagrees in some way, large or  small.
>>
>> True. But then we might not always be able to see the real reason. After all, his CV looks better than ours!
> 
> Well, in most cases I tend to agree with Walter, but, it's not due to his CV.
> 
>> Also, some of the reasons
>> might take a lot of explaining, especially if they're subtle. All
>> that is time away from actually writing the D compiler.   .-)
> 
> True, and we don't want that. :0)
> 
>> And sometimes it may be just as simple as a feature demanding more research and writing from Walter, than what it's worth.
> 
> True, which is why it's up to us to attempt to show how useful it can be, then he has some idea whether it's worth looking at.
> 
>>>> I certainly don't disagree with Walter; actually I think D is the best  language I've ever seen (at least in the compiled-to-native-code  category).
>>
>> Me too! And an ever increasing number of my friends here!
> 
> I think most people here, are here for this very reason. Or they think D will become the best and want to follow it's evolution.
> 
>>>> Well, considering how opCast is the only problem where not having return-type-overloads is really an issue, perhaps it could just get special treatment and the problem'd be solved?
>>
>> FTR: I still have no opinion for or against overloading on return type.
> 
> Fair enough.
> 
>> Can we find examples where not having it produces cumbersome code compared with having RTO?
> 
> Lets use opCast as an example. I think writing a class which you want to be convertable to several different basic types is a case where this would be useful, for example...
> 
> class SomeNewType {
>    int opCast() {}
>    float opCast() {}
> }
> 
> void main() {
>    SomeNewType p = new SomeNewType();
>    int a;
>    float b;
> 
>    a = p;  //error cannot implicitly cast from SomeNewType to int
>    b = p;  //error cannot implicitly cast from SomeNewType to float
>    a = cast(int)p;    //ok
>    b = cast(float)p;  //ok
> }
> 
> to achieve this, now, you need to use a seperate function for each i.e.
> 
> class SomeNewType {
>    int toInt() {}
>    float toFloat() {}
> }
> 
> Not bad per-se. But, it leaves one wondering what the point of opCast is. Maybe the rationale is that you should only need 1 opCast, as in you cast to x, and all the other types you need should be castable from that type? If so, then why does int cast to more than 1 type? surely these cases are comparable?
> 
> Lets consider it when writing a template, i.e. generic programming.
> 
> template foo(T) {
>    void foo(T p) {
>      int a = cast(int)p;  //would be possible
>      int a = p.toInt();   //ok
>    }
> }
> 
> but lets go further and try...
> 
> template foo(T, NT) {
>    void foo(T p, out NT q) {
>      q = cast(typeof(NT))p;  //would be possible
>      q = p.to???();          //impossible?
>    }
> }
> 
> Granted, it seems reasonably rare for most programs to need to do this, there may be workarounds?
> 
> Another possible use is optimisation, it may be possible for your class to return types x, y and z more efficiently than returning x and having something else convert from x to y or x to z. i.e. maybe you're caching those values. or simply producing them from scratch is faster than converting from an existing value of another type.
> 
> Overall I think this is a minor feature, and maybe it's more work than it's worth. But then, maybe someone can come up with a really good use for it. I've done my best to lay it all out for everyone as I see it.
> 
> Regan

I could use such a feature today (yesterday actually).

-- 
Derek
Melbourne, Australia
23/02/2005 9:39:53 AM
February 22, 2005
I still think the signature for opCast should change to something like:
  bit opCast(inout T result);

Return 'true' on success, 'false' on failure (could not cast for some reason).  Then we could have as many opCasts as we need.

My apologies if this has come up already.

-- Chris S
February 22, 2005
Regan Heath wrote:

> 
> No, you missunderstand. The behaviour isn't changing at all. Cast still  does what cast does, the intent of the programmer is what changes.
cast currently takes one type an converts it to another type.  What you are suggesting would make cast select a different function based on the return type.  Surely that changes the role of cast?  Instead of simply being a type conversion mechanism, it is now a mechanism for resolving ambiguity.

> 
>> Also, what happens in the situation where:
>> int foo(int i) {};    <--- I really want this one called
>> char foo (int i) {};
>>
>> char c = cast(char)(cast(int)foo(10));
>>
>> Ugly, no?
> 
> 
> Perhaps, matter of opinion.
> 
> Your example seems engineered to look bad however. Why do you have a "char  foo" function, yet want to use the "int foo" one, to get a char? It makes  little or no sense to me.
> 
> Regan
It is engineered, yes - but that doesn't mean that it couldn't happen in the real world.

Brad
February 22, 2005
On Wed, 23 Feb 2005 11:51:22 +1300, <brad@domain.invalid> wrote:
> Regan Heath wrote:
>
>>  No, you missunderstand. The behaviour isn't changing at all. Cast still  does what cast does, the intent of the programmer is what changes.
> cast currently takes one type an converts it to another type.  What you are suggesting would make cast select a different function based on the return type.  Surely that changes the role of cast?

Nope. It's comparable to casting a parameter. eg.

void foo(int a)   {}
void foo(float a) {}

foo(cast(int)5);

cast still converts the type, which is it's role/function, but, the intent (of the programmer) here is to select the overload and resolve the ambiguity.

The only difference is the order in which things happen, I suspect people (you?) think the thing that happens first must be the intent, I disagree.

> Instead of simply being a type conversion mechanism, it is now a mechanism for resolving ambiguity.

It already is, that's part of my point, it's already used to resolve ambiguity, see the above example.

>>> Also, what happens in the situation where:
>>> int foo(int i) {};    <--- I really want this one called
>>> char foo (int i) {};
>>>
>>> char c = cast(char)(cast(int)foo(10));
>>>
>>> Ugly, no?
>>   Perhaps, matter of opinion.
>>  Your example seems engineered to look bad however. Why do you have a "char  foo" function, yet want to use the "int foo" one, to get a char? It makes  little or no sense to me.
>>  Regan
> It is engineered, yes - but that doesn't mean that it couldn't happen in the real world.

True. I'd argue that if it did you have a bigger 'design' problem.

Regan
February 22, 2005
On Tue, 22 Feb 2005 16:53:50 -0600, Chris Sauls <ibisbasenji@gmail.com> wrote:
> I still think the signature for opCast should change to something like:
>    bit opCast(inout T result);
>
> Return 'true' on success, 'false' on failure (could not cast for some reason).  Then we could have as many opCasts as we need.

Interesting.. how would you use it? same as other types i.e.

cast(Type)object;

if so, where/how do you get the return value?
I assume the inout gets assigned to the lhs?

> My apologies if this has come up already.

I don't remember it, but I've not been here as long as some, and my memory isn't perfect.

Regan