On Tuesday, 3 June 2014 at 22:05:29 UTC, TheFlyingFiddle wrote:
Well one reason for this is that unlike methods it is hard to
resolve ambiguity between diffrent operator overloads that have
been defined in diffrent modules.
Example: 2D-vectors
//vector.d
struct Vector
{
float x, y;
}
//cartesian.d
Vector opBinary(string op : "+")(ref Vector lhs, ref Vector rhs)
{
//Code for adding two cartesian vectors.
}
//polar.d
Vector opBinary(string op : "+")(ref Vector lhs, ref Vector rhs)
{
//Code for adding two polar vectors.
}
//main.d
import polar, vector;
void main()
{
auto a = Vector(2, 5);
auto b = Vector(4, 10);
auto c = a + b; //What overload should we pick here?
//This ambiguity could potentially be resolved like this:
auto d = polar.opBinary!"+"(a, b);
//But... This defeats the whole purpose of operators.
}
Interesting point. However, I believe this is also the case for
ordinary functions used with UFCS:
// cartesian.d
Vector add(ref Vector lhs, ref Vector rhs)
{ /* Code for adding two cartesian vectors. */ }
// polar.d
Vector add(ref Vector lhs, ref Vector rhs)
{ /* Code for adding two polar vectors. */ }
// main.d
import polar, vector;
void main()
{
auto a = Vector(2, 5);
auto b = Vector(4, 10);
auto c = a.add(b); // What overload should we pick here?
// The fully qualified form looks quite different,
// but it's rare, and intentionally explicit about what's
// really going on, so that's OK.
auto d = polar.add(a, b);
}
It strikes me that ambiguous cases are not nearly as common as
unambiguous cases, especially if you use selective imports.
I believe Julia uses a re-write rule for its operators: the first
thing the compiler does when evaluating an operator expression is
re-write it as a function call; after that, all the language's
resolution rules apply as they would to any other function call.
D could easily take this approach, simplifying the language while
maintaining backwards-compatibility.
Side note:
You can achieve what you want to do with template mixins.
...
While this implementation is not as clean as global operator
overloading it works today and it makes it very simple to add
operators to new types of grids.
Thanks for the example. This is actually my current solution :)
However, as you allude to, I'm not a huge fan of it because
1) It requires boilerplate on the part of library users.
2) It makes you scratch your head and go "Huh; operator and
non-operator functions have different compile-time polymorphism
capabilities; why is that?"