www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D1 operator overloads have been deprecated.

reply uranuz <neuranuz gmail.com> writes:
In change log of nightly builds I see that:
`D1 operator overloads have been deprecated`
The major concern about it is that D2 style operators are 
template functions. And template functions cannot be virtual. 
Does it mean that we shall not be able to decalare operators in 
classes and interfaces that could be overloaded in terms of OOP. 
What is the proposed solution to this problemme?

Currently the only way I see is to declare `final` 
opOpAssign(RHS, string op)(RHS rhs) (for instance). And then 
dispatch them to `regular` virtual functions. But it shall look 
the same as these operator that we are trying to deprecate, but 
with extra `wrapper`. What is the profit of deprecating these D1?

Or is it meant that virtual operators in generally a bad 
practice?! But I don't understand - why?
Jul 11 2019
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11.07.19 19:58, uranuz wrote:
 In change log of nightly builds I see that:
 `D1 operator overloads have been deprecated`
 The major concern about it is that D2 style operators are template 
 functions. And template functions cannot be virtual. Does it mean that 
 we shall not be able to decalare operators in classes and interfaces 
 that could be overloaded in terms of OOP. What is the proposed solution 
 to this problemme?
 
 Currently the only way I see is to declare `final` opOpAssign(RHS, 
 string op)(RHS rhs) (for instance). And then dispatch them to `regular` 
 virtual functions. But it shall look the same as these operator that we 
 are trying to deprecate, but with extra `wrapper`.
import std.stdio; class C{ int x; this(int x){ this.x=x; } C opAddImpl(C rhs){ return new C(x+rhs.x); } alias opBinary(string op:"+")=opAddImpl; } void main(){ auto a=new C(1),b=new C(2); writeln((a+b).x); } You can probably even write a mixin template that automatically upgrades your class from D1 style operators to D2 style.
 What is the profit of deprecating these D1?
 ...
When designing a language from scratch, probably you wouldn't add two ways to declare operators, especially if one of them subsumes the other.
Jul 11 2019
next sibling parent Nathan S. <no.public.email example.com> writes:
On Thursday, 11 July 2019 at 19:07:28 UTC, Timon Gehr wrote:
     alias opBinary(string op:"+")=opAddImpl;
Nice workaround. That shouldn't be necessary though. Something so fundamental shouldn't require 'tricks'.
Jul 11 2019
prev sibling parent reply Bert <Bert gmail.com> writes:
On Thursday, 11 July 2019 at 19:07:28 UTC, Timon Gehr wrote:
 On 11.07.19 19:58, uranuz wrote:
 In change log of nightly builds I see that:
 `D1 operator overloads have been deprecated`
 The major concern about it is that D2 style operators are 
 template functions. And template functions cannot be virtual. 
 Does it mean that we shall not be able to decalare operators 
 in classes and interfaces that could be overloaded in terms of 
 OOP. What is the proposed solution to this problemme?
 
 Currently the only way I see is to declare `final` 
 opOpAssign(RHS, string op)(RHS rhs) (for instance). And then 
 dispatch them to `regular` virtual functions. But it shall 
 look the same as these operator that we are trying to 
 deprecate, but with extra `wrapper`.
import std.stdio; class C{ int x; this(int x){ this.x=x; } C opAddImpl(C rhs){ return new C(x+rhs.x); } alias opBinary(string op:"+")=opAddImpl; } void main(){ auto a=new C(1),b=new C(2); writeln((a+b).x); } You can probably even write a mixin template that automatically upgrades your class from D1 style operators to D2 style.
 What is the profit of deprecating these D1?
 ...
When designing a language from scratch, probably you wouldn't add two ways to declare operators, especially if one of them subsumes the other.
um, the OP said "works with virtual functions/inheritance". When are people going to learn that aliases are not virtual and have nothing to do with preserving inheritance structure? import std.stdio; class X { } class C : X{ int x; this(int x){ this.x=x; } C opAddImpl(C rhs){ return new C(x+rhs.x); } alias opBinary(string op:"+")=opAddImpl; } void main(){ X a=new C(1),b=new C(2); writeln((a+b).x); } fails.
Jul 12 2019
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12.07.19 15:42, Bert wrote:
 
 um, the OP said "works with virtual functions/inheritance". When are 
 people going to learn that aliases are not virtual and have nothing to 
 do with preserving inheritance structure?
 
 
 import std.stdio;
 
 class X
 {
 }
 class C : X{
      int x;
      this(int x){ this.x=x; }
      C opAddImpl(C rhs){
          return new C(x+rhs.x);
      }
      alias opBinary(string op:"+")=opAddImpl;
 }
 void main(){
      X a=new C(1),b=new C(2);
      writeln((a+b).x);
 }
 
 fails.
Please do enlighten us how you would make that work with D1 operators.
Jul 12 2019
parent Bert <Bert gmail.com> writes:
On Friday, 12 July 2019 at 15:07:11 UTC, Timon Gehr wrote:
 On 12.07.19 15:42, Bert wrote:
 
 um, the OP said "works with virtual functions/inheritance". 
 When are people going to learn that aliases are not virtual 
 and have nothing to do with preserving inheritance structure?
 
 
 import std.stdio;
 
 class X
 {
 }
 class C : X{
      int x;
      this(int x){ this.x=x; }
      C opAddImpl(C rhs){
          return new C(x+rhs.x);
      }
      alias opBinary(string op:"+")=opAddImpl;
 }
 void main(){
      X a=new C(1),b=new C(2);
      writeln((a+b).x);
 }
 
 fails.
Please do enlighten us how you would make that work with D1 operators.
I said nothing about getting it to work. It works import std.stdio; abstract class X { //abstract X opNeg(); // D1 abstract X opNegImpl(); // D2 alias opUnary(string op : "-") = opNegImpl; // D2 } class C : X { int x; this(int x) { this.x = x; } //override C opNeg(){ return new C(-this.x); } D1 override C opNegImpl() { return new C(-x); } } void main() { X a = new C(1), b = new C(2); writeln((cast(C)(-b)).x); } There is nothing wrong with D1 operators! Unless there is a deeper problem besides just freeing names, what is being done is a whole lot of old D code will be broke simply to free opXXX id's... This type of thing is called *making a small problem much bigger*. Maybe the better way would be to convert the D1 op names to opD1XXX then at least a simple search and replace can solve 99% of the problems... of course, there is no real problem because someone will come along and complain that the id name space is polluted with opD1XXX's and must be removed... at some point someone has to stand up and say "It's not an issue but breaking hundreds of thousands of lines of library code is!". If you think opD1XXX is too common, then _op_D1_OLD_OPERATOR_NAME__XXX... It's irrelevant, but deprecating something just for the hell of it is wrong and that is what *seems* to be done here(I'm going off what J.D. said about there being two ways to do the same way, the old D1 way with fixed opXXX and the new way with opXXX templates, which, are in fact essentially identical semantically with a slight syntactical difference... sorta like {} VS BEGIN and END.)
Jul 13 2019
prev sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 12 July 2019 at 13:42:35 UTC, Bert wrote:
 um, the OP said "works with virtual functions/inheritance". 
 When are people going to learn that aliases are not virtual and 
 have nothing to do with preserving inheritance structure?
You're misunderstanding. import std.stdio; abstract class X { abstract X opAddImpl(X rhs); alias opBinary(string op:"+") = opAddImpl; } class C : X{ int x; this(int x){ this.x=x; } override C opAddImpl(X rhs){ return new C(x+(cast(C) rhs).x); } } void main(){ X a=new C(1),b=new C(2); writeln((cast(C) (a+b)).x); }
Jul 12 2019
parent reply Bert <Bert gmail.com> writes:
On Friday, 12 July 2019 at 15:21:10 UTC, FeepingCreature wrote:
 On Friday, 12 July 2019 at 13:42:35 UTC, Bert wrote:
 um, the OP said "works with virtual functions/inheritance". 
 When are people going to learn that aliases are not virtual 
 and have nothing to do with preserving inheritance structure?
You're misunderstanding. import std.stdio; abstract class X { abstract X opAddImpl(X rhs); alias opBinary(string op:"+") = opAddImpl; } class C : X{ int x; this(int x){ this.x=x; } override C opAddImpl(X rhs){ return new C(x+(cast(C) rhs).x); } } void main(){ X a=new C(1),b=new C(2); writeln((cast(C) (a+b)).x); }
This works but now requires modifying the entire oop structure to conform. This is now how oop is suppose to work or good design decisions. We are talking about a pre-existing oop design that might be 100's of classes in size. It's all fine and dandy when you are designing something from scratch but since D1 essentially does this anyways, what's the point of depreciating it then requiring it? https://digitalmars.com/d/1.0/operatoroverloading.html import std.stdio; abstract class X { //abstract X opNeg(); // D1 abstract X opNegImpl(); // D2 alias opUnary(string op : "-") = opNegImpl; // D2 } class C : X { int x; this(int x) { this.x = x; } //override C opNeg(){ return new C(-this.x); } D1 override C opNegImpl() { return new C(-x); } } void main() { X a = new C(1), b = new C(2); writeln((cast(C)(-b)).x); } So, all that has been achieved is allowing one to use their own base function names. A whole shit load of code is going to be broke SIMPLY to remove the default opXXX names... names that would rarely be used in any code for any other purpose anyways(I've never in my live used an id starting with op that was not meant to be used as an operator). Absolutely nothing is achieved if the only issue is "there are "two" ways of doing it" as J.D. says. In the new way we have to have an alias redirector and the function rather than just the function. All we end up getting is freeing a member id name and a whole lot of broken code.
Jul 13 2019
next sibling parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Sunday, 14 July 2019 at 05:27:28 UTC, Bert wrote:
 In the new way we have to have an alias redirector and the 
 function rather than just the function. All we end up getting 
 is freeing a member id name and a whole lot of broken code.
It should be easy to fix by implementing an interface with D2 templated operators that forward to D1. The downside would be that you'll need to add the interface to each class/interface with existing D1 operators.
Jul 15 2019
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Monday, 15 July 2019 at 08:00:26 UTC, Alexandru Ermicioi wrote:
 On Sunday, 14 July 2019 at 05:27:28 UTC, Bert wrote:
 In the new way we have to have an alias redirector and the 
 function rather than just the function. All we end up getting 
 is freeing a member id name and a whole lot of broken code.
It should be easy to fix by implementing an interface with D2 templated operators that forward to D1. The downside would be that you'll need to add the interface to each class/interface with existing D1 operators.
And an even better alternative would be to use mixin templates that introspect aggregate entity for d1 operators, and define d2 forwarders. Then fixing woukd be just one one for each aggregate: mixin D1OperatorFix!().
Jul 15 2019
parent reply Daniel N <no public.email> writes:
On Monday, 15 July 2019 at 08:07:37 UTC, Alexandru Ermicioi wrote:
 On Monday, 15 July 2019 at 08:00:26 UTC, Alexandru Ermicioi 
 wrote:
 On Sunday, 14 July 2019 at 05:27:28 UTC, Bert wrote:
 In the new way we have to have an alias redirector and the 
 function rather than just the function. All we end up getting 
 is freeing a member id name and a whole lot of broken code.
Just forward to the legacy D1 name. *) Only have to change base class *) Backwards and forwards compatible import std.stdio; abstract class X { abstract X opNeg(); // D1 alias opUnary(string op : "-") = opNeg; // D2 } class C : X { int x; this(int x) { this.x = x; } override C opNeg() { return new C(-x); } } void main() { X a = new C(1), b = new C(2); writeln((cast(C)(-b)).x); }
Jul 15 2019
parent reply uranuz <neuranuz gmail.com> writes:
Hello! I think that this descision to deprecated operators was 
made without analysis about consequences that we will have for 
classes that use this operator. I think there should be a DIP 
with analysis of the problemme. Because in this discussion we had 
a talk about different workarounds to solve the problemme that we 
actually created by ourselves by accepting this "improvement" 
without proper analysis.
Benefits of this descision are completely unclear for user of the 
language.
It's very very strange to introduce breaking changes that way..
Oct 06 2019
parent reply uranuz <neuranuz gmail.com> writes:
Now user of language must actually have two sets of functions 
with proposed workaround with classes. The final template 
function and the old-style D1 (yes we can rename it and so on, 
but this doesn't change so much). We should have two entities 
instead of one that we had. So this is very "contradictive" 
improvement.
Oct 06 2019
parent reply Gregor =?UTF-8?B?TcO8Y2ts?= <gregormueckl gmx.de> writes:
On Sunday, 6 October 2019 at 08:34:40 UTC, uranuz wrote:
 Now user of language must actually have two sets of functions 
 with proposed workaround with classes. The final template 
 function and the old-style D1 (yes we can rename it and so on, 
 but this doesn't change so much). We should have two entities 
 instead of one that we had. So this is very "contradictive" 
 improvement.
You only need this workaround if you habe a class hierarchy where overloaded operators are overridden in subclasses. This is a very narrow set of cases. Otherwise, implementing only the D2 way is sufficient. How often do you actually need to override operator behaviour in subclasses? I'm genuinely curious about use cases.
Oct 06 2019
parent reply uranuz <neuranuz gmail.com> writes:
On Sunday, 6 October 2019 at 10:39:04 UTC, Gregor Mückl wrote:
 On Sunday, 6 October 2019 at 08:34:40 UTC, uranuz wrote:
 Now user of language must actually have two sets of functions 
 with proposed workaround with classes. The final template 
 function and the old-style D1 (yes we can rename it and so on, 
 but this doesn't change so much). We should have two entities 
 instead of one that we had. So this is very "contradictive" 
 improvement.
You only need this workaround if you habe a class hierarchy where overloaded operators are overridden in subclasses. This is a very narrow set of cases. Otherwise, implementing only the D2 way is sufficient. How often do you actually need to override operator behaviour in subclasses? I'm genuinely curious about use cases.
Personally, I have several places in my code where I need to add `in` into basic interface and implement it in derived classes. It is not some "weird", "narrow" set of cases, I believe. For instance, I have different implementations of `in` in all of these classes. The main problemme for me is that you need to change the basic interface, that is used by all the classes. It is not very good, especially if anybody still cannot explain what benefits it gives me. And also you (maybe) need to fix all of the derived implementations... Every time I do something I want to know: what for? Especially, if it touches some basic aspects. But still I haven't got any answer...
Oct 06 2019
next sibling parent Gregor =?UTF-8?B?TcO8Y2ts?= <gregormueckl gmx.de> writes:
On Sunday, 6 October 2019 at 12:28:57 UTC, uranuz wrote:
 On Sunday, 6 October 2019 at 10:39:04 UTC, Gregor Mückl wrote:
 You only need this workaround if you habe a class hierarchy 
 where overloaded operators are
 overridden in subclasses. This is a very narrow set of cases. 
 Otherwise, implementing only the D2 way is sufficient. How 
 often do you actually need to override operator behaviour in 
 subclasses? I'm genuinely curious about use cases.
Personally, I have several places in my code where I need to add `in` into basic interface and implement it in derived classes. It is not some "weird", "narrow" set of cases, I believe. For instance, I have different implementations of `in` in all of these classes.
What is your use case for an in operator that changes in derived classes?
Oct 06 2019
prev sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Sunday, October 6, 2019 6:28:57 AM MDT uranuz via Digitalmars-d wrote:
 Every time I do something I want to know: what for? Especially,
 if it touches some basic aspects. But still I haven't got any
 answer...
It was the plan to remove the D1 operator overloading functions when the D2, templated operators were added to the language. The D1 operators have only been around this long, because no one got around to deprecating them. Having multiple ways to overload operators complicates the language and provides almost no benefit. The only thing that you can't do with the templated operators that you can do with the D1 operators is have them be virtual, which doesn't matter for the vast majority of D code, since most D code uses structs rather than classes. And for the code that does use classes and actually needs the operators to be virtual, it's trivial to just put the overloaded operator in the base class and then have protected, virtual functions that the derived classes override. It's what you'd do with the NVI (non-virtual inheritance) pattern anyway. So, code that uses classes and needs virtual, overloaded operators is going to be in the minority, and there's a clear and easy way to use virtual functions with the templated operators. As such, the extra complication of having the D1 operators in the language is not considered to be worth it. - Jonathan M Davis
Oct 06 2019
parent uranuz <neuranuz gmail.com> writes:
On Monday, 7 October 2019 at 03:23:27 UTC, Jonathan M Davis wrote:
...
 And for the code that does use classes and actually needs the 
 operators to be virtual, it's trivial to just put the 
 overloaded operator in the base class and then have protected, 
 virtual functions that the derived classes override. It's what 
 you'd do with the NVI (non-virtual inheritance) pattern anyway.
Thanks. Understood. The problemme is just that this solution feels like some ugly hack (in Russian language we call it "костыль"). But seems that there is nothing to do with this already. No matter...
Oct 07 2019
prev sibling parent aliak <something something.com> writes:
On Sunday, 14 July 2019 at 05:27:28 UTC, Bert wrote:
 On Friday, 12 July 2019 at 15:21:10 UTC, FeepingCreature wrote:
 On Friday, 12 July 2019 at 13:42:35 UTC, Bert wrote:
 um, the OP said "works with virtual functions/inheritance". 
 When are people going to learn that aliases are not virtual 
 and have nothing to do with preserving inheritance structure?
You're misunderstanding. import std.stdio; abstract class X { abstract X opAddImpl(X rhs); alias opBinary(string op:"+") = opAddImpl; } class C : X{ int x; this(int x){ this.x=x; } override C opAddImpl(X rhs){ return new C(x+(cast(C) rhs).x); } } void main(){ X a=new C(1),b=new C(2); writeln((cast(C) (a+b)).x); }
This works but now requires modifying the entire oop structure to conform. This is now how oop is suppose to work or good design decisions. We are talking about a pre-existing oop design that might be 100's of classes in size. It's all fine and dandy when you are designing something from scratch but since D1 essentially does this anyways, what's the point of depreciating it then requiring it? https://digitalmars.com/d/1.0/operatoroverloading.html import std.stdio; abstract class X { //abstract X opNeg(); // D1 abstract X opNegImpl(); // D2 alias opUnary(string op : "-") = opNegImpl; // D2 } class C : X { int x; this(int x) { this.x = x; } //override C opNeg(){ return new C(-this.x); } D1 override C opNegImpl() { return new C(-x); } } void main() { X a = new C(1), b = new C(2); writeln((cast(C)(-b)).x); } So, all that has been achieved is allowing one to use their own base function names. A whole shit load of code is going to be broke SIMPLY to remove the default opXXX names... names that would rarely be used in any code for any other purpose anyways(I've never in my live used an id starting with op that was not meant to be used as an operator). Absolutely nothing is achieved if the only issue is "there are "two" ways of doing it" as J.D. says. In the new way we have to have an alias redirector and the function rather than just the function. All we end up getting is freeing a member id name and a whole lot of broken code.
I'm curious where you get the "whole shit load of code is going to be broken" part? And what do you suggest, that D keep supporting all its legacy? Or is it "just this one"? You can think that it's just one little feature but it's not - it's a lot of small features that are duplicated, that have to be maintained, that get bug reports because they are supposed to be working, and that actively hinder the development and solidifying of more useful features and features that supercede older features. Bloat is bad.
Jul 15 2019
prev sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Thursday, 11 July 2019 at 17:58:50 UTC, uranuz wrote:
 In change log of nightly builds I see that:
 `D1 operator overloads have been deprecated`
 The major concern about it is that D2 style operators are 
 template functions. And template functions cannot be virtual.
Nice catch...
 Does it mean that we shall not be able to decalare operators in 
 classes and interfaces that could be overloaded in terms of OOP.
It rather looks like a big whoopsie. Nobody has thought to this case during the review stage[1]. I don't know if the change would have been accepted otherwise, even if they were not documented anymore. [1] https://github.com/dlang/dmd/pull/10130
Jul 11 2019
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 11 July 2019 at 20:29:19 UTC, Basile B. wrote:
 It rather looks like a big whoopsie. Nobody has thought to this 
 case during the review stage[1]
I remember it coming up, maybe not formally, but the solution of just forwarding the final templates to a virtual implementation, mentioned above, has been around for a while.
Jul 11 2019
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Jul 11, 2019 at 08:35:13PM +0000, Adam D. Ruppe via Digitalmars-d wrote:
 On Thursday, 11 July 2019 at 20:29:19 UTC, Basile B. wrote:
 It rather looks like a big whoopsie. Nobody has thought to this case
 during the review stage[1]
I remember it coming up, maybe not formally, but the solution of just forwarding the final templates to a virtual implementation, mentioned above, has been around for a while.
If it's a 1-line fix, I doubt it would stop W&A from going ahead anyway, since D1 operators have been deprecated for quite a while now. On a separate note, I think somebody mentioned a while ago that there are ways to make template functions virtual. If this is a really important issue, we should explore that instead of clinging on to old cruft that's supposed to have been gone years ago. T -- An elephant: A mouse built to government specifications. -- Robert Heinlein
Jul 11 2019
prev sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, July 11, 2019 2:29:19 PM MDT Basile B. via Digitalmars-d wrote:
 On Thursday, 11 July 2019 at 17:58:50 UTC, uranuz wrote:
 In change log of nightly builds I see that:
 `D1 operator overloads have been deprecated`
 The major concern about it is that D2 style operators are
 template functions. And template functions cannot be virtual.
Nice catch...
 Does it mean that we shall not be able to decalare operators in
 classes and interfaces that could be overloaded in terms of OOP.
It rather looks like a big whoopsie. Nobody has thought to this case during the review stage[1]. I don't know if the change would have been accepted otherwise, even if they were not documented anymore. [1] https://github.com/dlang/dmd/pull/10130
It was decided years ago that the old, non-templated overloaded operators would be removed from the language, and it was well-known that if you then wanted virtual operator overloading, you'd need to forward to a protected, virtual function. Most user-defined types in D are structs anyway, and it's a simple workaround when you need virtual operator overloading with classes. I actually thought that the old operators had been fully removed quite some time ago, but I guess that it's on the list of stuff where we clearly decided that it was going away, but no one got around to actually deprecating it. - Jonathan M Davis
Jul 11 2019