www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Which operators cannot be overloaded and why not?

reply NonNull <non-null use.startmail.com> writes:
Which operators cannot be overloaded and why not?
Sep 13 2021
next sibling parent reply user1234 <user1234 12.de> writes:
On Monday, 13 September 2021 at 14:12:36 UTC, NonNull wrote:
 Which operators cannot be overloaded and why not?
Let's start the list. - `new` and `delete` Because operators are overloaded in aggregates new was supported but not anymore, the idea is that a an aggregate should not be tied a specific allocator. - post incr/decr See [https://dlang.org/spec/operatoroverloading.html#postincrement_postdecrement_operat rs](explantations). This is an arbitrary limitation imho but the current way enfore that the side effect is not apllied to the expression result. - condition al expression ` cond ? exp : exp ` No idea why it is not supported... nobody has never complained tho ;) - `typeid` Same... the result of typeid has to be a TypeInfoClass, so there's not much to do... exactness is expected - `is` (identity) could be but I suppose that this stuff needs to be correct to keep code consistent what else ?
Sep 13 2021
next sibling parent reply jfondren <julian.fondren gmail.com> writes:
On Monday, 13 September 2021 at 14:33:03 UTC, user1234 wrote:
 - condition al expression ` cond ? exp : exp `
And many other boolean operators, unary !, binary && and || https://dlang.org/spec/operatoroverloading.html lists all the overloadable operators, and https://dlang.org/spec/expression.html has all the operators, so "which operators" is a matter of close comparison. "why not" is much harder to answer.
Sep 13 2021
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 13 September 2021 at 14:42:42 UTC, jfondren wrote:
 On Monday, 13 September 2021 at 14:33:03 UTC, user1234 wrote:
 - condition al expression ` cond ? exp : exp `
And many other boolean operators, unary !, binary && and || https://dlang.org/spec/operatoroverloading.html lists all the overloadable operators, and https://dlang.org/spec/expression.html has all the operators, so "which operators" is a matter of close comparison. "why not" is much harder to answer.
I think the intent is for the boolean operators to be handled via `opCast!bool`.
Sep 13 2021
parent user1234 <user1234 12.de> writes:
On Monday, 13 September 2021 at 14:59:38 UTC, Paul Backus wrote:
 On Monday, 13 September 2021 at 14:42:42 UTC, jfondren wrote:
 On Monday, 13 September 2021 at 14:33:03 UTC, user1234 wrote:
 - condition al expression ` cond ? exp : exp `
And many other boolean operators, unary !, binary && and || https://dlang.org/spec/operatoroverloading.html lists all the overloadable operators, and https://dlang.org/spec/expression.html has all the operators, so "which operators" is a matter of close comparison. "why not" is much harder to answer.
I think the intent is for the boolean operators to be handled via `opCast!bool`.
I didn't see it was already suggested and answered the same... but yet that's exactly the case.
Sep 13 2021
prev sibling next sibling parent Basile B. <b2.temp gmx.com> writes:
On Monday, 13 September 2021 at 14:42:42 UTC, jfondren wrote:
 On Monday, 13 September 2021 at 14:33:03 UTC, user1234 wrote:
 - condition al expression ` cond ? exp : exp `
And many other boolean operators, unary !, binary && and || https://dlang.org/spec/operatoroverloading.html lists all the overloadable operators, and https://dlang.org/spec/expression.html has all the operators, so "which operators" is a matter of close comparison. "why not" is much harder to answer.
Oh! I have never noticed that `&&` and `||`, despite of being quite "ordinary" binary ops are not overloadable. In [styx](https://styx-lang.gitlab.io/styx/attribute.html#operatorattribute) that works because, although inspired by the D way, in the sense that overloads are implemented in custom types, the selection is done using an expression template ```d struct S { operator(a && b) function andAnd(T other): auto {return false} } ``` So as long as the expression in the attribute argument looks like a valid expression the stuff is found (and the supprot code in the compiler is super simple), e.g with code in a body: ```d e1 && e2; // look if e1 is an aggregate and if it contains operator(a && b) e1 + e2; // look if e1 is an aggregate and if it contains operator(a && b) ``` While D uses specific identifier + templates value param for strings. (note that only the equivalent of D opBinary**Right** works...) anyway. zorry for this off-topic.
Sep 13 2021
prev sibling next sibling parent reply user1234 <user1234 12.de> writes:
On Monday, 13 September 2021 at 14:42:42 UTC, jfondren wrote:
 On Monday, 13 September 2021 at 14:33:03 UTC, user1234 wrote:
 - condition al expression ` cond ? exp : exp `
And many other boolean operators, unary !, binary && and ||
They are all indirectly supported if `opCast` is overloaded: ```d unittest { struct S { bool opCast(T = bool)(){return true;} } S s; assert(s && s); } ``` so this is a bit like the postincr case.
Sep 13 2021
parent user1234 <user1234 12.de> writes:
On Monday, 13 September 2021 at 15:29:05 UTC, user1234 wrote:
 [...]
 so this is a bit like the postincr case.
i.e "prevent the semantics to be hijacked".
Sep 13 2021
prev sibling parent reply NonNull <non-null use.startmail.com> writes:
On Monday, 13 September 2021 at 14:42:42 UTC, jfondren wrote:
 On Monday, 13 September 2021 at 14:33:03 UTC, user1234 wrote:
 - condition al expression ` cond ? exp : exp `
And many other boolean operators, unary !, binary && and || https://dlang.org/spec/operatoroverloading.html lists all the overloadable operators, and https://dlang.org/spec/expression.html has all the operators, so "which operators" is a matter of close comparison.
Is there no list of such in an article online? It would be good if that work was done for once and for all, with a short explanation in each case, possibly with discussion in some cases of how to achieve results by other means.
Sep 13 2021
parent user1234 <user1234 12.de> writes:
On Monday, 13 September 2021 at 18:06:42 UTC, NonNull wrote:
 On Monday, 13 September 2021 at 14:42:42 UTC, jfondren wrote:
 On Monday, 13 September 2021 at 14:33:03 UTC, user1234 wrote:
 - condition al expression ` cond ? exp : exp `
And many other boolean operators, unary !, binary && and || https://dlang.org/spec/operatoroverloading.html lists all the overloadable operators, and https://dlang.org/spec/expression.html has all the operators, so "which operators" is a matter of close comparison.
Is there no list of such in an article online? It would be good if that work was done for once and for all, with a short explanation in each case, possibly with discussion in some cases of how to achieve results by other means.
well this whole thread is certainly the raw material for an article (or D blog post) on D operator overloads. Just, someone has to compile the informations and write the said article.
Sep 13 2021
prev sibling parent reply user1234 <user1234 12.de> writes:
On Monday, 13 September 2021 at 14:33:03 UTC, user1234 wrote:
 what else ?
when you have ```d alias AA1 = int[int]; alias AA2 = AA1[int]; ``` then you can write ```d AA2 aa; aa[0] = [0 : 0]; aa[0][0] = 0; ``` The `[0][0]` cannot be expressed using operator overloads (and a custom map type that implements opIndexAssign).But this case is rather due to the fact that druntime seems to do something a bit unusal here (according to an old discussion that happend once on irc).
Sep 13 2021
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/13/21 10:47 AM, user1234 wrote:
 On Monday, 13 September 2021 at 14:33:03 UTC, user1234 wrote:
 what else ?
when you have ```d alias AA1 = int[int]; alias AA2 = AA1[int]; ``` then you can write ```d AA2 aa; aa[0] = [0 : 0]; aa[0][0] = 0; ``` The `[0][0]` cannot be expressed using operator overloads (and a custom map type that implements opIndexAssign).But this case is rather due to the fact that druntime seems to do something a bit unusal here (according to an old discussion that happend once on irc).
This is because the compiler calls a different hook depending on the usage of the expression `aa[0]`. Which one it calls is not consistent. There isn't an analog for indexing overloads. A further example: ```d int[int] aa; aa[0]++; // ok void foo(ref int x) {x++;} foo(aa[1]); // range violation ``` -Steve
Sep 13 2021
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Sep 13, 2021 at 02:12:36PM +0000, NonNull via Digitalmars-d-learn wrote:
 Which operators cannot be overloaded and why not?
Others have already given the list, so I won't repeat that. As to the "why": In general, D tries to avoid the wild wild west, every operator for himself situation in C++ that leads to unreadable, unmaintainable code. For example, C++ allows you to separately overload <, <=, ==, >, >=, !=. That means the programmer is free to define == and != in a way completely inconsistent with each other, for example. D solves this by combining == and != into a single operator overload, opEquals, where != is the negation of ==. This ensures == and != are always consistent with each other. Same with <, <=, >, >=: C++ lets you overload each one separately, potentially in a completely inconsistent way with each other, so that somebody reading your code has no idea whether x < y implies y >= x, or whether x < y implies y > x. Again, unreadable code. D solves this by combining all these operators into a single overload: opCmp. This ensures consistency between all of these relative comparison operators. Similarly, prefix ++/-- and postfix ++/-- cannot be separately overloaded; they are combined into a single overload, where postfix ++/--, as in `x++`, is simply rewritten by the compiler to the equivalent of: (){ auto tmp = x; x++; return tmp; }() (Of course, this is just to illustrate the semantics. The compiler doesn't actually create a lambda here.) This ensures that prefix and postfix operators behave in the expected way, so that ++x and x++ don't behave in two wildly different, unrelated ways, like it sometimes happens in C++ code because C++ lets you define separate overloads for them. Combining these operator overloads have the additional benefit that less overloads are needed to implement a numerical type: instead of needing to separately implement ==, !=, <, <=, >, >=, you only have to implement two overloads, opEquals and opCmp, and the compiler takes care of the rest. Similarly, ++ and -- only need to be implement once each, and you'll get the postfix varieties for free. Furthermore, the boolean operators !, &&, || are not directly overloadable. Again, C++ lets you separately overload them, which guarantees that when you see (what looks like) a boolean expression in C++, you have no idea what its true semantics are, because && could mean something completely different from the usual meaning. D doesn't let you overload these operators, thereby ensuring that a boolean expression remains a boolean expression: !, &&, || always have the standard semantics. What D *does* let you do is to define opCast!bool yourself, so that you can define how a user-defined type converts to a bool, which can then participate in the !, &&, || operators in the usual way. In general, the philosophy in D is that operator overloading really should be reserved for number-like (or math-like) objects, and overloaded operators are expected to behave more-or-less like their standard, non-overloaded meanings. Operator overloading of the variety that C++ likes to do, like iostream's <<, >> overloads, are frowned upon, because they obscure the surface meaning of the code and make it hard to understand and maintain. (Incidentally, D *does* allow you to overload '<<' and '>>'. It's just frowned upon to overload them in a way that doesn't in someway represent bit-shifting.) Furthermore, overloaded operators are expected to behave analogously w.r.t. each other, e.g., == and != should behave like opposites of each other; < should not have completely unrelated semantics to >, and so on. T -- The right half of the brain controls the left half of the body. This means that only left-handed people are in their right mind. -- Manoj Srivastava
Sep 13 2021
parent reply NonNull <non-null use.startmail.com> writes:
On Monday, 13 September 2021 at 16:12:34 UTC, H. S. Teoh wrote:
 On Mon, Sep 13, 2021 at 02:12:36PM +0000, NonNull via 
 Digitalmars-d-learn wrote:
 Which operators cannot be overloaded and why not?
Others have already given the list, so I won't repeat that.
I didn't see unary &. Maybe others are missing.
 As to the "why":

 In general, D tries to avoid the wild wild west, every operator 
 for himself situation in C++ that leads to unreadable, 
 unmaintainable code.
Thanks for this explanation. The consolidation you mention is great!
Sep 13 2021
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Sep 13, 2021 at 06:19:20PM +0000, NonNull via Digitalmars-d-learn wrote:
 On Monday, 13 September 2021 at 16:12:34 UTC, H. S. Teoh wrote:
 On Mon, Sep 13, 2021 at 02:12:36PM +0000, NonNull via
 Digitalmars-d-learn wrote:
 Which operators cannot be overloaded and why not?
Others have already given the list, so I won't repeat that.
I didn't see unary &. Maybe others are missing.
[...] IIRC, unary & cannot be overloaded for the same reasons &&, ||, and ! cannot be overloaded: it leads to hard-to-understand, hard-to-maintain code. Incidentally, C++ *does* allow overloading of &, ostensibly because some wrapper types might want to use it to become transparent. But one of the consequences of this is that now you need a std::address-of pseudo-operator that *actually* takes an address of something when you *really* mean to take the address of the wrapper object instead of the wrapped object. So & loses its original meaning and has to be replaced by a std:: hack. Which, to me, is abundant proof that overloading & was a bad idea in the first place. // BTW, as an aside, as a example of why every-man-for-himself, wild-wild-west operator overloading in C++ is a bad idea, consider these two lines of C++ code: fun<A, B>(a, b); gun<T, U>(a, b); What do they mean? Ostensibly, these are function calls to two template functions, specifying explicit template arguments. Unfortunately, that is not the case: only *one* of these lines is a template function call, the other is something else altogether. But nobody can tell the difference unless they read the entire context, as shown below. (Incidentally, this example also shows why C++'s choice of template syntax was a poor one.) Compile and run with a C++ compiler to find out what the actual behaviour is. ------- // Totally evil example of why C++ template syntax and free-for-all // operator overloading is a Bad, Bad Idea. #include <iostream> struct Bad { }; struct B { }; struct A { Bad operator,(B b) { return Bad(); } }; struct D { }; struct Ugly { D operator>(Bad b) { return D(); } } U; struct Terrible { } T; struct Evil { ~Evil() { std::cout << "Hard drive reformatted." << std::endl; } }; struct Nasty { Evil operator,(D d) { return Evil(); } }; struct Idea { void operator()(A a, B b) { std::cout << "Good idea, data saved." << std::endl; } Nasty operator<(Terrible t) { return Nasty(); } } gun; template<typename T, typename U> void fun(A a, B b) { std::cout << "Have fun!" << std::endl; } int main() { A a; B b; // What do these lines do? fun<A, B>(a, b); gun<T, U>(a, b); } ------- T -- Your inconsistency is the only consistent thing about you! -- KD
Sep 14 2021