Thread overview
Operator Overloading - Only for Classes and Structs?
4 days ago
matheus
4 days ago
monkyyy
4 days ago
matheus
3 days ago
Lance Bachmeier
3 days ago
Andy Valencia
3 days ago
Nick Treleaven
3 days ago
Nick Treleaven
3 days ago
matheus
4 days ago
Hi, could someone please tell me why operador overloading (https://dlang.org/spec/operatoroverloading.html) is relegated only to classes and structs?

"1 Operator overloading is accomplished by rewriting operators whose operands are class or struct objects into calls to specially named members. No additional syntax is used."

I'd like to overload "~" to concatenate a string with a integer and do something else,  like:

  auto s = "1" ~ 2;

But what I've tried so far didn't work.

If that constraint (Classes and Structs) is true, why?

Thanks,

Matheus.
4 days ago
On Sunday, 22 June 2025 at 15:04:27 UTC, matheus wrote:
> Hi, could someone please tell me why operador overloading (https://dlang.org/spec/operatoroverloading.html) is relegated only to classes and structs?
>
> "1 Operator overloading is accomplished by rewriting operators whose operands are class or struct objects into calls to specially named members. No additional syntax is used."
>
> I'd like to overload "~" to concatenate a string with a integer and do something else,  like:
>
>   auto s = "1" ~ 2;
>
> But what I've tried so far didn't work.
>
> If that constraint (Classes and Structs) is true, why?
>
> Thanks,
>
> Matheus.

The explanations you'll get never satisfied me. I feel like safety is thrown around when the spec is often written to match the previous behavior

If you need to get something working inside some already written code I suggest alias this for bandaids; opdispath for major redesigns.
4 days ago
On Sunday, June 22, 2025 9:04:27 AM Mountain Daylight Time matheus via Digitalmars-d-learn wrote:
> Hi, could someone please tell me why operador overloading (https://dlang.org/spec/operatoroverloading.html) is relegated only to classes and structs?
>
> "1 Operator overloading is accomplished by rewriting operators whose operands are class or struct objects into calls to specially named members. No additional syntax is used."
>
> I'd like to overload "~" to concatenate a string with a integer and do something else,  like:
>
>    auto s = "1" ~ 2;
>
> But what I've tried so far didn't work.
>
> If that constraint (Classes and Structs) is true, why?

Overloaded operators are considered to be part of the type just like constructors are part of the type, and D has done a number of things with overloaded operators to try to minimize the ability to do what many consider to be overloaded operator abuse (e.g. using using overloaded operators to define a DSL - or really much of anything that's not trying to behave like the operators do for built-in types). Making it so that you can't add overloaded operators anywhere but directly to the type itself helps minimize that abuse (though there are certainly folks who would like to do it, and there have been discussions / arguments about it in the past).

And honestly, concatenating a string and an integer is exactly the sort of thing that D was trying to avoid - and it's also why ~ is used for concatenation rather than +, because otherwise there's potentially confusion over stuff like `"1" + "2"` (e.g. should it be "12" or "3"?). Adding the ability to do `"1" ~ 2` would be similarly confusing. Should 2 be treated as "2", or should it be treated as `cast(char) 2` (or `cast(dchar) 2`, though with a number that small, it would be the same thing)?

In general, the point of overloaded operators is to make it so that user-defined types can have some of the same operations as built-in types with semantics which are consistent with those operators for built-in types. There are cases where that could be done with overloaded operators defined outside of the type if that were allowed, but changing how an operator works for a built-in type definitely goes against what the ability to overload operators is intended for.

- Jonathan M Davis




4 days ago
On Monday, 23 June 2025 at 10:14:25 UTC, Jonathan M Davis wrote:
> ...
>
> Overloaded operators are considered to be part of the type just like constructors are part of the type, and D has done a number of things with overloaded operators to try to minimize the ability to do what many consider to be overloaded operator abuse (e.g. using using overloaded operators to define a DSL - or really much of anything that's not trying to behave like the operators do for built-in types). Making it so that you can't add overloaded operators anywhere but directly to the type itself helps minimize that abuse (though there are certainly folks who would like to do it, and there have been discussions / arguments about it in the past).
>
> And honestly, concatenating a string and an integer is exactly the sort of thing that D was trying to avoid - and it's also why ~ is used for concatenation rather than +, because otherwise there's potentially confusion over stuff like `"1" + "2"` (e.g. should it be "12" or "3"?). Adding the ability to do `"1" ~ 2` would be similarly confusing. Should 2 be treated as "2", or should it be treated as `cast(char) 2` (or `cast(dchar) 2`, though with a number that small, it would be the same thing)?
>
> In general, the point of overloaded operators is to make it so that user-defined types can have some of the same operations as built-in types with semantics which are consistent with those operators for built-in types. There are cases where that could be done with overloaded operators defined outside of the type if that were allowed, but changing how an operator works for a built-in type definitely goes against what the ability to overload operators is intended for.
>
> - Jonathan M Davis

Well that was nicely explained.

I wonder if this kind of explanation should be added to the manual.

Thanks,

Matheus.
3 days ago

On Sunday, 22 June 2025 at 15:04:27 UTC, matheus wrote:

>

Hi, could someone please tell me why operador overloading (https://dlang.org/spec/operatoroverloading.html) is relegated only to classes and structs?

One reason is that it would be hard to reason about what the operator means - for example if there is one operator overload in module a and another one in b, that have compatible operand types, and both imports are used in the same project for different modules.

Restricting operator overloading to the type definition avoids this problem.

That in itself might not be too much of a problem, but then any expression may implicitly convert to a type that has an operator overload defined. It can get hard to see what is going on, and free function operator overloads would complicate this.

>

"1 Operator overloading is accomplished by rewriting operators whose operands are class or struct objects into calls to specially named members. No additional syntax is used."

I'd like to overload "~" to concatenate a string with a integer and do something else, like:

auto s = "1" ~ 2;

That is already valid code, because int implicitly converts to dchar, which can be appended to a string. E.g. "1" ~ 42 gives "1*". (BTW I don't think integer types should implicitly convert to character types). I trust you see how implicit conversions can get complicated in combination with operators.

3 days ago

On Monday, 23 June 2025 at 15:27:46 UTC, Nick Treleaven wrote:

>

One reason is that it would be hard to reason about what the operator means - for example if there is one operator overload in module a and another one in b, that have compatible operand types, and both imports are used in the same project for different modules.

Or rather in the same module.

...

>

That is already valid code, because int implicitly converts to dchar, which can be appended to a string.

Or concatenated, in this case.

3 days ago
On Monday, 23 June 2025 at 15:27:46 UTC, Nick Treleaven wrote:
> ...
> One reason is that it would be hard to reason about what the operator means - for example if there is one operator overload in module `a` and another one in `b`, that have compatible operand types, and both imports are used in the same project for different modules.
> ...

I haven't thought about that in fact. That another thing with what Jonathan said which makes a lot of sense.

In fact this restriction is good in fact.

Thanks,

Matheus.
3 days ago

On Monday, 23 June 2025 at 10:14:25 UTC, Jonathan M Davis wrote:

>

D has done a number of things with overloaded operators to try to minimize the ability to do what many consider to be overloaded operator abuse (e.g. using using overloaded operators to define a DSL - or really much of anything that's not trying to behave like the operators do for built-in types). Making it so that you can't add overloaded operators anywhere but directly to the type itself helps minimize that abuse (though there are certainly folks who would like to do it, and there have been discussions / arguments about it in the past).

I think everyone agrees that pointers can be abused, misused, and used to make things blow up. Yet nobody argues they should be removed, because it's up to the programmer to do things that make sense. The restrictions on operator overloading reflect the use cases of the language designers and nothing more. That's the case with all language design decisions, but we shouldn't pretend it's based on any special insight.

The reality is that it forces the programmer to add hacks to get around it. You end up doing things like

struct MyString {
string s;
alias s this;
}

struct MyInt {
int i;
alias i this;
}

and then you overload operators on those structs. I'm not sure I'd call that a win when the only argument behind it is that the programmer can't be trusted to write proper code. (User-defined precedence is a different matter completely. There are no meaningful benefits to it when you can use parens. That seems to be a big reason people say operator overloading is bad.)

3 days ago

I was a little surprised the subclassing syntax didn't do structural concatenation for struct's. That is,

import std.stdio : writeln;

struct A {
 int a;
 void printA() {
  println(this.a);
 }
}
struct B : A {
 int b;
 void printAB() {
  this.printA();
  println(this.b);
 }
}

void an_A_fun(A arg) {
 arg.printA();
}

void main() {
 B foo;
 foo.a = 1;
 foo.b = 2;
 foo.printAB();
 an_A_fun(foo);
}

Unlike classes, this would guarantee storage adjacency, and still preserve its behavior as a value rather than an object reference. And then you get to share code for common initial parts of a given struct.

3 days ago
On Monday, June 23, 2025 9:56:15 PM Mountain Daylight Time Andy Valencia via Digitalmars-d-learn wrote:
> I was a little surprised the subclassing syntax didn't do structural concatenation for struct's.  That is,
>
> ```d
> import std.stdio : writeln;
>
> struct A {
>   int a;
>   void printA() {
>    println(this.a);
>   }
> }
> struct B : A {
>   int b;
>   void printAB() {
>    this.printA();
>    println(this.b);
>   }
> }
>
> void an_A_fun(A arg) {
>   arg.printA();
> }
>
> void main() {
>   B foo;
>   foo.a = 1;
>   foo.b = 2;
>   foo.printAB();
>   an_A_fun(foo);
> }
> ```
>
> Unlike classes, this would guarantee storage adjacency, and still
> preserve its behavior as a value rather than an object reference.
>   And then you get to share code for common initial parts of a
> given struct.

There has been talk of implementing something along those lines as a replacement for `alias this`, since `alias this` has proven to be quite problematic in practice (though we likely wouldn't actually remove `alias this`, unfortunately, due to the desire to avoid breaking existing code). However, it wouldn't have any sort of implicit conversion component as part of it. It would just copy code from the "base" struct to the "derived" struct as a way to share code. The idea would be to just copy code rather than actually creating any sort of subtype.

What you've shown there with allowing a B to be passed as an A is called object slicing, and avoiding that is one of the main reasons that structs and classes were separated in D in the first place. It's a source of bugs in C++.

- Jonathan M Davis