Thread overview
Without D1-style operator overloads, the `in` operator for classes doesn't make sense
Jan 06, 2020
Don
Jan 06, 2020
Ontonator
Jan 06, 2020
Don
January 06, 2020
In DMD 2.088.0, the D1 operator overloads were deprecated, in favour of the D2 syntax which has been available for years.

For example, opAdd_r is replaced with opBinaryRight(string op)(...) if (op == "+")

But this is not purely a syntactic change. Some functionality is lost: the D2 syntax is a template; and therefore, it is not a virtual function.
It was argued that using virtual functions for operators is an obscure feature which hardly anybody would use. So it is not a significant loss. And I agree with that, *for the operators which D inherited from C*.

Unfortunately, this argument is not valid for two operators which are unique to D: opCat and opIn. The problem is most acute with the `in` operator.

Suppose you make an object-oriented container class library.
Let's say you have a Set. This may have derived classes HashSet, BloomFilterSet, etc.

One of the most basic operations is asking if an element is a member of the set. For sure you would want to use the `in` operator. But you can't, because it isn't virtual.

So, I think we've got ourselves into a situation which doesn't make sense. It's pretty much always a mistake to define a D2-style 'in' operator for a class. If you are using classes, you would want it to be virtual; if you don't need it to be virtual, you shouldn't be using classes.

So I feel that by introducing this deprecation, we've dropped support for OOP container classes.
If this is part of a grand conspiracy to remove classes from D, I won't stand in the way :) But apparently we've done this by accident, which is rather alarming.



January 06, 2020
On Monday, 6 January 2020 at 10:55:15 UTC, Don wrote:
> So I feel that by introducing this deprecation, we've dropped support for OOP container classes.
I would say: create an issue for that - it's not gone yet, only deprecated. So should be revertible without problems.
January 06, 2020
On Monday, 6 January 2020 at 10:55:15 UTC, Don wrote:
> In DMD 2.088.0, the D1 operator overloads were deprecated, in favour of the D2 syntax which has been available for years.
>
> [...]

I’m on my phone right now, so I can’t check, but could you emulate a virtual operator by creating a virtual method (e.g. `contains`) which is called from the operator and which child classes could override? It isn’t as ergonomic, but it should work.
January 06, 2020
On 1/6/20 6:16 AM, Ontonator wrote:
> On Monday, 6 January 2020 at 10:55:15 UTC, Don wrote:
>> In DMD 2.088.0, the D1 operator overloads were deprecated, in favour of the D2 syntax which has been available for years.
>>
>> [...]
> 
> I’m on my phone right now, so I can’t check, but could you emulate a virtual operator by creating a virtual method (e.g. `contains`) which is called from the operator and which child classes could override? It isn’t as ergonomic, but it should work.

In fact this is exactly why this change was allowed (and this exact problem was discussed):

class C
{
   int* opIn(string key); // virtual

   auto opBinaryRight(string op : "in", X)(X key) // wrapper
   {
       return opIn(key);
   }
}

So when/if the D1 operators get removed, you put this wrapper in, and it works.

In fact, you can easily make a mixin template that will implement all the D2 style operators when you have defined the D1 style ones.

-Steve
January 06, 2020
On Monday, 6 January 2020 at 14:14:35 UTC, Steven Schveighoffer wrote:
> On 1/6/20 6:16 AM, Ontonator wrote:
>> On Monday, 6 January 2020 at 10:55:15 UTC, Don wrote:
>>> In DMD 2.088.0, the D1 operator overloads were deprecated, in favour of the D2 syntax which has been available for years.
>>>
>>> [...]
>> 
>> I’m on my phone right now, so I can’t check, but could you emulate a virtual operator by creating a virtual method (e.g. `contains`) which is called from the operator and which child classes could override? It isn’t as ergonomic, but it should work.
>
> In fact this is exactly why this change was allowed (and this exact problem was discussed):
>
> class C
> {
>    int* opIn(string key); // virtual
>
>    auto opBinaryRight(string op : "in", X)(X key) // wrapper
>    {
>        return opIn(key);
>    }
> }
>
> So when/if the D1 operators get removed, you put this wrapper in, and it works.


Indeed, you have implement something like that to support the 'in' syntax on any class.
But it's only a partial workaround.
It's an ugly hack, and since there's no way to do it without breaking other code (see below), why would you even do that?
You are better off just creating a normal function like `contains`, the same as you would do in C++.


Note that I am singling out opIn and opCat/opCatAssign. These are fundamentally different from the normally binary operators.
A large part of the motivation for creating the D2-style overloads was the recognition that the code for '+' and '-' is almost always the same. Even '*', '/', '<<', will often share code with '+'. Merging them gets rid of a lot of code duplication.


But that isn't true for 'in' and '~'. We've made them syntactically similar, but semantically they are unrelated and they would normally share no code with the arithmetic and logical operators. In fact it's pretty rare for an object to define both '+' and 'in', for example.
They are much more closely related to opIndex and opSlice.

('in' is semantically similar to opIndex, in many ways).


> In fact, you can easily make a mixin template that will implement all the D2 style operators when you have defined the D1 style ones.

That's blocked as a direct migration path, because deprecation warnings are generated if you use the D1 names. (And no, you cannot turn off deprecation warnings, that turns off *all* deprecation warnings).

So you have to use names which are different from the D1 names. That creates a new problem. If you've deployed this as a library (which is very likely in collection classes!), anybody who has derived a class from your library class, and overridden opIn, now needs to rename their opIn to use your new name. You'd like to create a deprecation message to tell them how to do this, but is there even a way to do that?

(Incidentally, this is a illustration of why a deprecation process can make things much, much worse than directly removing something).

In practice, support for the 'in' operator in classes has been dropped. Pretty much silently.

This is a major breaking change, it's not discussed in the changelog (the changelog advocates a mechanical substitution which doesn't work).

To make matters worse, there was no stability branch before this release. So to get critical security/wrong code bugfixes, you need to make this complicated change that propagates to code you don't even control.

I think we've made a mistake here.

January 06, 2020
On 1/6/20 12:25 PM, Don wrote:
> On Monday, 6 January 2020 at 14:14:35 UTC, Steven Schveighoffer wrote:

>> In fact, you can easily make a mixin template that will implement all the D2 style operators when you have defined the D1 style ones.
> 
> That's blocked as a direct migration path, because deprecation warnings are generated if you use the D1 names. (And no, you cannot turn off deprecation warnings, that turns off *all* deprecation warnings).
> 

This is actually a bug IMO. If you define opBinaryRight!"in", then opIn_r (yes, I got that wrong in the example) should not be used by the compiler. opIn_r after all is a valid function name, and there's no reason it needs to be marked as deprecated if not called via the operator.

If not filed, someone should file it. Same goes for all the D1 operators.

-Steve
January 06, 2020
On 1/6/20 1:22 PM, Steven Schveighoffer wrote:
> This is actually a bug IMO. If you define opBinaryRight!"in", then opIn_r (yes, I got that wrong in the example) should not be used by the compiler. opIn_r after all is a valid function name, and there's no reason it needs to be marked as deprecated if not called via the operator.
> 
> If not filed, someone should file it. Same goes for all the D1 operators.

https://issues.dlang.org/show_bug.cgi?id=20486

-Steve