operator overloading outside the type
Steven Schveighoffer
schveiguy at gmail.com
Sat Mar 29 03:01:32 UTC 2025
On Friday, 28 March 2025 at 08:57:39 UTC, Jonathan M Davis wrote:
> I've been against the idea ever since it was first brought up,
> and I continue to be against the idea. Overloaded operators are
> not normal functions, and they're not used like normal
> functions. Their syntax is fundamentally different, and if
> there were ever an import conflict because of it, you can't
> resolve it. At least with UFCS, you can choose to call the
> function normally instead so that you can give the full import
> path, but if you do that with an overloaded operator, you might
> as well not be using an overloaded operator.
You can call the operator, it's just a normal function.
```d
a.opBinary!"+"(b);
```
But yeah, if you have conflicts, you did something wrong
(probably imported conflicting modules).
This does not disqualify the idea, or horrify me at all. We have
the same thing with UFCS members, it's fine.
> Overloaded operators already have a number of restrictions on
> them to try to enforce that they behave in a sensible way and
> aren't abused. They're special and IMHO should be treated as
> such.
This proposal doesn't change that at all. I'm not proposing
removing any of those restrictions.
The idea would work just like any other UFCS function - if there
is an existing member, that gets used. If not, then you move onto
UFCS possibilities. Making the `opBinary` exclusively a rewrite
is actually easier to explain than the current stuff.
> I don't want to live in a world where operators start behaving
> differently based on what was or wasn't imported. They're
> supposed to be a core part of the type, not just a function
> that accepts the type.
I think your life is worth more than that, Jonathan.
In all seriousness, people said the same thing about member
functions when UFCS was proposed. It has not been an issue, and
the results have been fantastic. This would have the same power,
as you are making the syntax more appealing and convenient. I'd
rather do `a + b + c` instead of `a.add(b).add(c)`. operator
precedence also makes things easier to understand.
Note that `opBinaryRight` exists, and this allows you to "add"
more operator overloads to a completed type, so it's not
*completely* without precedent.
> Imagine the weird side effects that you'd get and hard to track
> down bugs if something like opCast were overloaded externally.
>
> And some operators clearly can't be overloaded externally -
> such as opEquals - because the compiler generates the code for
> them if they're not there. Others simply couldn't work in any
> sane fashion if they were external (e.g. opDispatch).
This brings up an ambiguity issue. Certain operators have
*default* implementations, and if those exist, the external
overloads should be masked.
Note also that if you define *one* `opBinary` overload member,
then zero external ones can work, unless you alias them in. This
is the same as UFCS, it just follows normal overload set and
scoping rules.
> IMHO, if you want to add operators to a type, then wrap it. It
> doesn't require making the language rules any more weird or
> confusing, and it's straightforward.
No, it's not. There is no implicit conversion, so you now have to
unwrap it explicitly wherever it's used, or add overloads to all
functions that accept the unwrapped type to take the wrapped type.
The use case I'm specifically looking at is POD types like
raylib's `Vector2`, which is a C struct without any operator
support. I don't want to deal with unwrapping that every time I
have to pass it into a raylib function, or wrap all those
functions to take the custom type.
The solution we currently have is to add the overloads to a
custom implementation of them. This doesn't work for importC at
all, which is why this is now way more important than it was
before.
-Steve
More information about the Digitalmars-d
mailing list