October 13, 2012
Another way to describe my reasoning...

According to TDPL, if var is a variable of a user-defined type, then:
++var
gets rewritten as:
var.opUnary!"++"()

Thus, it would be very logical to assume that it doesn't matter whether you write:
++var
...or, write the following instead:
var.opUnary!"++"()
...because the second form is what the first form gets written to anyway.

But, that "very logical assumption" turns out to be wrong. Because in D, as it stands currently, it *does* make a difference whether you write it using the first form or the second:

struct S
{
    int _value;
}

ref S opUnary(string op : "++")(ref S s)
{
    ++s._value;
    return s;
}

Now, writing the following compiles and works:
S var;
var.opUnary!"++"();

...while the following doesn't compile:
S var;
++var;

This behavior of the language is not logical. And I don't think that logic is a matter of preference or taste.
October 13, 2012
On 10/13/2012 06:02 PM, Maxim Fomin wrote:
> ...
> Different groups of people have different mind and same things produce
> different sense on them. From my point of view operator overloading
> methods are special functions and not treating them as candidates for
> UFCS does make more sense.

I do not understand how an operation that happens to be called '+' is fundamentally different from an operation that is called 'add'.

> Even if you convince in your opinion,
> language addition without applied purposes makes no benefit.

I guess the functionality could be achieved in DMD mostly by removing
code. (Code for good error messages excluded!)
October 13, 2012
On Saturday, October 13, 2012 19:01:26 Tommi wrote:
> Another way to describe my reasoning...
> 
> According to TDPL, if var is a variable of a user-defined type,
> then:
> ++var
> gets rewritten as:
> var.opUnary!"++"()
> 
> Thus, it would be very logical to assume that it doesn't matter
> whether you write:
> ++var
> ...or, write the following instead:
> var.opUnary!"++"()
> ...because the second form is what the first form gets written to
> anyway.
> 
> But, that "very logical assumption" turns out to be wrong. Because in D, as it stands currently, it *does* make a difference whether you write it using the first form or the second:
> 
> struct S
> {
>      int _value;
> }
> 
> ref S opUnary(string op : "++")(ref S s)
> {
>      ++s._value;
>      return s;
> }
> 
> Now, writing the following compiles and works:
> S var;
> var.opUnary!"++"();
> 
> ...while the following doesn't compile:
> S var;
> ++var;
> 
> This behavior of the language is not logical. And I don't think that logic is a matter of preference or taste.

All that TDPL is telling you is that the compiler uses "lowering" on overloaded operators to generate their code. It lowers them to calls the appropriate functions. But it does so in a way consistent with how the operators are supposed to work. Preincrement and Postincrement are fundamentally different. D makes the wise choice of making it so that you simply overload increment and then has the compiler use that function in a manner that generates code which is preincrementing or postincrementing depending on which operator was used. This guarantees that preincrement and postincrement are consistent. This is in contrast to C++ where you could make them do totally different things. Not only does that avoid weird bugs, but it makes it so that the compiler can generate more efficient code too. This is because postincrementing generates a temporary to save the original value for the expression where it's being used whereas preincrement does not, and if the compiler knows that preincrement and postincrement are semantically the same, then it can replace postincrement with preincrement when it doesn't matter which is called. In D, because you overload _one_ operator, the compiler knows this for user-defined types, but in C++, it doesn't, and can't make that optimization. So, in C++, code like

for(vector<int>::iterator i = v.begin(), e = v.end; i != e; i++) {}

is stuck creating a temporary for every call to i++, whereas in D, it can be replaced with ++i. Similarly, in D, >, >=, <=, and < are all translated to calls to opCmp, making it so that you overload one function but get 4 operators.

There are cases where it would just be broken for the compiler to simply call your overloaded operator function without doing extra stuff to ensure that it acted like the built-in operators (incrementing being a prime example). So no, it's _not_ simply a matter of calling your overloaded operator functions. It's just that part of the process of compiling code using overloaded operators is to translate it to code which involves calling the overloaded operator functions. That translation may or may not be direct. TDPL makes a point about it to show that the compiler is able to translate the overloaded operators into function calls and the compile those instead of having to go to all of the extra effort required to deal with fully compiling the overloaded operators directly. It's just much simpler to turn one language construct into another, existing language construct, and then compile that rather than having to understand how to compile both. The same happens with other language constructs as well (e.g. scope statements).

TDPL _never_ says that syntactic sugar is applicable to lowered code. Lowering code is effectively an implementation detail of the compiler that makes its life easier. It does _not_ make it so that one language construct will be translated into another where it will then be assumed that the new language construct is using syntactic sugar such as UFCS, because _all_ of that syntactic sugar must be lowered to code which _isn't_ syntactic sugar anymore. It would be far more expensive to have to continually make passes to lower code over and over again until no more lowering was required than it would be to just have to lower it once.

You're reading way to much into what TDPL is saying. It's simply telling you about how the compiler goes about translating code which uses operators such as +, >, or = into the functions that you used to overload them. It's _not_ telling you that it'll do UFCS on overloaded operator functions. Heck, technically, TDPL never really says that D _has_ UFCS. It talks about the member call function syntax for _arrays_ (which D had for ages before it had UFCS), not for types in general. It's only very recently that full UFCS has been added to the language.

Both overloaded operators and UFCS use lowering to generate different code which the compiler then compiles, but they _aren't_ mixed and they will _never_ be mixed. If it had _ever_ been intended that it be possible to overload operators as free functions, then we'd simply have made it so that you could declare a free function like

auto opBinary(string op)(Foo foo, Bar bar)
{
    ...
}

in the first place without requiring that it be a member function. But it _was_ required to be a member function, and it would make no sense to allow a new feature to circumvent that restriction. If it was supposed to be circumventable, then the restriction wouldn't have been put there in the first place.

- Jonathan M Davis
October 13, 2012
On 10/13/2012 10:15 PM, Jonathan M Davis wrote:
> ...
> construct is using syntactic sugar such as UFCS, because _all_ of that
> syntactic sugar must be lowered to code which _isn't_ syntactic sugar anymore.

That is not what lowering means.

> It would be far more expensive to have to continually make passes to lower
> code over and over again until no more lowering was required than it would be
> to just have to lower it once.
> ...

It does not have to be implemented it in an inefficient way. It is
actually simpler for a sane compiler implementation to make UFCS apply
to lowered overloaded operators than to restrict it.

> You're reading way to much into what TDPL is saying. It's simply telling you
> about how the compiler goes about translating code which uses operators such
> as +, >, or = into the functions that you used to overload them. It's _not_
> telling you that it'll do UFCS on overloaded operator functions.

It is telling us that

a+b
is transformed to
a.opBinary!"+"(b)

UFCS applies to a.opBinary!"+"(b).

> Heck,
> technically, TDPL never really says that D _has_ UFCS. It talks about the
> member call function syntax for _arrays_ (which D had for ages before it had
> UFCS), not for types in general. It's only very recently that full UFCS has
> been added to the language.

Exactly, so what is the point? If TDPL does not talk about the UFCS
feature, then TDPL not talking about UFCS in the context of one
specific case certainly cannot be used as an argument to justify that
it should not apply in that case.

> Both overloaded operators and UFCS use lowering to generate different code
> which the compiler then compiles,

By using the same strategy recursively, otherwise it is not called lowering.

> but they _aren't_ mixed and they will
> _never_ be mixed. If it had _ever_ been intended that it be possible to
> overload operators as free functions, then we'd simply have made it so that
> you could declare a free function like
>
> auto opBinary(string op)(Foo foo, Bar bar)
> {
>     ...
> }
>
> in the first place  without requiring that it be a member function.

You can.

> But it _was_
> required to be a member function, and it would make no sense to allow a new
> feature to circumvent that restriction. If it was supposed to be
> circumventable, then the restriction wouldn't have been put there in the first
> place.

This argument is even less convincing in the context of an informally
specified language with a somewhat buggy reference compiler.
October 13, 2012
On Sun, Oct 14, 2012 at 12:12:01AM +0200, Timon Gehr wrote:
> On 10/13/2012 10:15 PM, Jonathan M Davis wrote:
[...]
> >but they _aren't_ mixed and they will _never_ be mixed. If it had _ever_ been intended that it be possible to overload operators as free functions, then we'd simply have made it so that you could declare a free function like
> >
> >auto opBinary(string op)(Foo foo, Bar bar)
> >{
> >    ...
> >}
> >
> >in the first place  without requiring that it be a member function.
> 
> You can.
> 
> >But it _was_ required to be a member function, and it would make no sense to allow a new feature to circumvent that restriction. If it was supposed to be circumventable, then the restriction wouldn't have been put there in the first place.
> 
> This argument is even less convincing in the context of an informally specified language with a somewhat buggy reference compiler.

OK, before this thread devolves into a shouting match, I'd like to understand what was the rationale behind this restriction. What were the reasons behind not allowing a non-member function to overload an operator? What are the pros and cons considered at the time, and how do they weigh now? Or was it just a matter of not being implemented because nobody thought about it at the time?


T

-- 
Why can't you just be a nonconformist like everyone else? -- YHL
October 13, 2012
On 10/14/2012 12:36 AM, H. S. Teoh wrote:
> On Sun, Oct 14, 2012 at 12:12:01AM +0200, Timon Gehr wrote:
>> On 10/13/2012 10:15 PM, Jonathan M Davis wrote:
> [...]
>>> but they _aren't_ mixed and they will _never_ be mixed. If it had
>>> _ever_ been intended that it be possible to overload operators as
>>> free functions, then we'd simply have made it so that you could
>>> declare a free function like
>>>
>>> auto opBinary(string op)(Foo foo, Bar bar)
>>> {
>>>     ...
>>> }
>>>
>>> in the first place  without requiring that it be a member function.
>>
>> You can.
>>
>>> But it _was_ required to be a member function, and it would make no
>>> sense to allow a new feature to circumvent that restriction. If it
>>> was supposed to be circumventable, then the restriction wouldn't have
>>> been put there in the first place.
>>
>> This argument is even less convincing in the context of an informally
>> specified language with a somewhat buggy reference compiler.
>
> OK, before this thread devolves into a shouting match, I'd like to
> understand what was the rationale behind this restriction. What were the
> reasons behind not allowing a non-member function to overload an
> operator? What are the pros and cons considered at the time, and how do
> they weigh now? Or was it just a matter of not being implemented because
> nobody thought about it at the time?
>
>
> T
>

Afaik free-function operator overloads (but not in the context of
UFCS) were considered and turned down because D did not want to get
amidst discussions about adding Koenig lookup. UFCS does not do Koenig
lookup.
October 14, 2012
On Saturday, 13 October 2012 at 22:34:19 UTC, H. S. Teoh wrote:
> OK, before this thread devolves into a shouting match, I'd like to
> understand what was the rationale behind this restriction. What were the
> reasons behind not allowing a non-member function to overload an
> operator? What are the pros and cons considered at the time, and how do
> they weigh now? Or was it just a matter of not being implemented because
> nobody thought about it at the time?
>
>
> T

It likely was not implemented rather than disallowed. The only mentioned reason is to allow writing operator overloading methods outside type scope - just because somebody (currently two people) consider it logical to broaden UFCS usage. This doesn't solve ay practical issue.
October 14, 2012
On Saturday, 13 October 2012 at 17:01:27 UTC, Tommi wrote:
> Another way to describe my reasoning...
>
> According to TDPL, if var is a variable of a user-defined type, then:
> ++var
> gets rewritten as:
> var.opUnary!"++"()

Not always. If user-defined type has an alias this to integer member, than something different would happen. It would be also interesting to see, how operation ++T would differ because somebody imported module with opUnary method. Because opUnary suits better than alias this, dmd will issue call to that function, it it see its declaration.

October 14, 2012
On Sunday, 14 October 2012 at 06:22:03 UTC, Maxim Fomin wrote:
> On Saturday, 13 October 2012 at 17:01:27 UTC, Tommi wrote:
>> Another way to describe my reasoning...
>>
>> According to TDPL, if var is a variable of a user-defined type, then:
>> ++var
>> gets rewritten as:
>> var.opUnary!"++"()
>
> Not always. If user-defined type has an alias this to integer member, than something different would happen.

Yeah, I wasn't specific enough with that example.

> It would be also interesting to see, how operation ++T would differ because somebody imported module with opUnary method. Because opUnary suits better than alias this, dmd will issue call to that function, it it see its declaration.

Actually, it seems that alias this has precedence over UFCS. So, a free function opUnary wouldn't ever suit better than an actual method opUnary of the thing referred to by that alias this.
October 14, 2012
On Saturday, 13 October 2012 at 19:50:02 UTC, Timon Gehr wrote:
> On 10/13/2012 06:02 PM, Maxim Fomin wrote:
>> ...
>> Different groups of people have different mind and same things produce
>> different sense on them. From my point of view operator overloading
>> methods are special functions and not treating them as candidates for
>> UFCS does make more sense.
>
> I do not understand how an operation that happens to be called '+' is fundamentally different from an operation that is called 'add'.

The first one is an operator, which sometimes, may be rewritten to function call, the second one is a function call.

>> Even if you convince in your opinion,
>> language addition without applied purposes makes no benefit.
>
> I guess the functionality could be achieved in DMD mostly by removing
> code. (Code for good error messages excluded!)

I don't understand what you are trying to say. Anyway, you can write a pull request and propose it at github. It would be interesting to see reaction of others.