digitalmars.D.learn - Virtual opBinary in interface
- sfp (45/45) Dec 19 Subject lines says it all, I think... The choice to make binary
- monkyyy (2/5) Dec 19 always try -i in response to any linker error
- user1234 (5/14) Dec 19 Function templates declared in interfaces are not virtual, see
- sfp (5/20) Dec 19 Thanks. Yes, I surmised as much. I'm wondering if there is an
- mzfhhhh (23/43) Dec 19 The template methods in the interface need to be implemented
- =?UTF-8?Q?Ali_=C3=87ehreli?= (16/17) Dec 20 Although you clearly have a need for, virtual operators haven't been
- =?UTF-8?Q?Ali_=C3=87ehreli?= (34/35) Dec 20 I remembered one such case. What should happen if both Cat and Dog
- sfp (23/32) Dec 21 Right, the semantics can be a little odd, but there are
- Steven Schveighoffer (9/17) Dec 20 As said virtual functions cannot be templates. However you can
- Andy Valencia (5/9) Dec 21 Thank you for that remarkably informative article! I picked up
- sfp (7/12) Dec 21 Thank you so much for posting this! This is exactly what I was
Subject lines says it all, I think... The choice to make binary operators implementable only via this `opBinary` template means it's unclear how to get virtual operators on an interface. E.g., this toy example *does* compile: ``` interface Scalar { Scalar opBinary(string op)(Scalar rhs); // wrong } class Int : Scalar { int i; this(int i) { this.i = i; } Int opBinary(string op)(Int rhs) if (op == "+") { return new Int(i + this.i); } } void main() { Scalar one = new Int(1); Scalar two = one + one; } ``` but with linker errors, of course: ``` ~[...] $ dmd scratch.d /usr/bin/ld: scratch.o: in function `_Dmain': scratch.d:(.text._Dmain[_Dmain]+0x2a): undefined reference to `_D7scratch6Scalar__T8opBinaryVAyaa1_2bZQtMFCQBqQBlZQi' collect2: error: ld returned 1 exit status Error: undefined reference to `scratch.Scalar scratch.Scalar.opBinary!("+").opBinary(scratch.Scalar)` referenced from `_Dmain` perhaps `.d` files need to be added on the command line, or use `-i` to compile imports Error: linker exited with status 1 cc scratch.o -o scratch -m64 -Xlinker --export-dynamic -L/usr/lib64 -Xlinker -Bstatic -lphobos2 -Xlinker -Bdynamic -lpthread -lm -lrt -ldl ``` Could someone set me straight here? I just want to be able to define an interface that has some binary ops and then overload them as needed. I am very new to D, and my goal is to learn how to solve this problem using classic, runtime, dynamic polymorphism in D (so, please don't suggest that I solve a different problem, suggest that I use a particular library, a different technique, etc.). Thanks in advance.
Dec 19
On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:perhaps `.d` files need to be added on the command line, or use `-i` to compile importsalways try -i in response to any linker error
Dec 19
On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:Subject lines says it all, I think... The choice to make binary operators implementable only via this `opBinary` template means it's unclear how to get virtual operators on an interface. E.g., this toy example *does* compile: ``` interface Scalar { Scalar opBinary(string op)(Scalar rhs); // wrong } [...]Function templates declared in interfaces are not virtual, see https://dlang.org/spec/interface.html#method-bodies (§17.1.1.2), so as `Scalar.opBinary` has no body the linker cannot find the matching function.
Dec 19
On Friday, 20 December 2024 at 01:29:32 UTC, user1234 wrote:On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:Thanks. Yes, I surmised as much. I'm wondering if there is an idiomatic way to accomplish "virtual binary operators in an interface". The `opBinary` template gets in the way of this in a way that e.g. C++'s `operator+` and friends do not.Subject lines says it all, I think... The choice to make binary operators implementable only via this `opBinary` template means it's unclear how to get virtual operators on an interface. E.g., this toy example *does* compile: ``` interface Scalar { Scalar opBinary(string op)(Scalar rhs); // wrong } [...]Function templates declared in interfaces are not virtual, see https://dlang.org/spec/interface.html#method-bodies (§17.1.1.2), so as `Scalar.opBinary` has no body the linker cannot find the matching function.
Dec 19
On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:Subject lines says it all, I think... The choice to make binary operators implementable only via this `opBinary` template means it's unclear how to get virtual operators on an interface. E.g., this toy example *does* compile: ``` interface Scalar { Scalar opBinary(string op)(Scalar rhs); // wrong } class Int : Scalar { int i; this(int i) { this.i = i; } Int opBinary(string op)(Int rhs) if (op == "+") { return new Int(i + this.i); } } void main() { Scalar one = new Int(1); Scalar two = one + one; } ```The template methods in the interface need to be implemented within the interface. https://dlang.org/spec/interface.html#method-bodies ```d interface Scalar { Scalar opBinary(string op)(Scalar rhs) if (op == "+") { return add(rhs); } Scalar add(Scalar rhs); } class Int : Scalar { int i; this(int i) { this.i = i; } Scalar add(Scalar rhs) { return new Int((cast(Int)rhs).i + this.i); } } void main() { Scalar one = new Int(1); Scalar two = one + one; } ```
Dec 19
On 12/19/24 10:49 AM, sfp wrote:Subject lines says it allAlthough you clearly have a need for, virtual operators haven't been common in my experience. I always felt they could cause semantic issues. For example, the two subclasses of an interface may not have the binary relation that the interface prescribes. I can think of the Animal hierarchy where Cat and Dog may have a certain relationship that may not make sense between Alligator and Mouse. Forcing such a binary function at the Interface level may not be right. Having said that, and to contradict myself, this discussion reminded me of a fun DConf presentation by our friend Jean-Louis Leroy that included examples of such virtual functionality where it made sense: "Open Methods for D (The Expression Problem - solved)" https://www.youtube.com/watch?v=MpwHeE2Vvfw&t=395s But it uses his magical implementation (enabled by D) of open methods (and multi-methods). Ali
Dec 20
On 12/20/24 10:40 AM, Ali Çehreli wrote:I always felt they could cause semantic issues.I remembered one such case. What should happen if both Cat and Dog defined the "+" operator? Should we expect 'cat + dog' behave the same as 'dog + cat'? Unfortunately, virtual functions are picked by the object that they are called on. The following example demonstrates this confusion with a function named mingleWith(). Different functions are called depending on the object. import std.stdio; interface Animal { void mingleWith(Animal); } class Dog : Animal { void mingleWith(Animal) { writeln("Dog with an Animal"); } } class Cat : Animal { void mingleWith(Animal) { writeln("Cat with an Animal"); } } void use(Animal a, Animal b) { a.mingleWith(b); b.mingleWith(a); // <-- DIFFERENT BEHAVIOR } void main() { auto c = new Cat(); auto d = new Dog(); use(c, d); } This is too much complication for engineering, program correctness, and life. :) Ali
Dec 20
On Friday, 20 December 2024 at 18:40:17 UTC, Ali Çehreli wrote:On 12/19/24 10:49 AM, sfp wrote:Right, the semantics can be a little odd, but there are definitely use cases for it which are very natural. Modeling how different kinds of animals mingle, I'm not sure... :-) "Virtual binary operators" are quite useful in computational science (my field...). For instance, with a numerical linear algebra library, you might have sparse matrices, dense matrices, diagonal matrices, Toeplitz matrices, matrices which are only accessible indirectly via their action (e.g. you multiply with a discrete Fourier transform matrix by applying the FFT and do not store the matrix itself), *block* matrices (comprised of other matrices), etc, etc. It is pretty common to mix and match these heterogeneously. MATLAB and Python have dense and sparse matrices, potentially other user-defined matrices/operators, and mixing and matching them is straightforward. Duck-typing is especially nice here, because there is no need to return a super type... multiply a dense matrix with a diagonal matrix and return a dense matrix (rather than a matrix supertype), no problem... I actually saw this multimethod implementation in another forum post when searching around, but not the talk. Thanks for posting it. I will definitely take a look, but unless I'm mistaken, D doesn't have free binary operators?Subject lines says it allAlthough you clearly have a need for, virtual operators haven't been common in my experience. I always felt they could cause semantic issues. ... But it uses his magical implementation (enabled by D) of open methods (and multi-methods). Ali
Dec 21
On Thursday, 19 December 2024 at 18:49:28 UTC, sfp wrote:Subject lines says it all, I think... The choice to make binary operators implementable only via this `opBinary` template means it's unclear how to get virtual operators on an interface.I am very new to D, and my goal is to learn how to solve this problem using classic, runtime, dynamic polymorphism in D (so, please don't suggest that I solve a different problem, suggest that I use a particular library, a different technique, etc.). Thanks in advance.As said virtual functions cannot be templates. However you can use alias to essentially define what you want your operators to be called. I wrote a blog post on how to use a single mixin to forward all operators to the D1 style overloads. You might find it useful or inspiring. https://www.schveiguy.com/blog/2022/06/how-to-keep-using-d1-operator-overloads/ -Steve
Dec 20
On Saturday, 21 December 2024 at 07:02:07 UTC, Steven Schveighoffer wrote:I wrote a blog post on how to use a single mixin to forward all operators to the D1 style overloads. You might find it useful or inspiring. https://www.schveiguy.com/blog/2022/06/how-to-keep-using-d1-operator-overloads/Thank you for that remarkably informative article! I picked up quite a number of new techniques from it. Andy
Dec 21
On Saturday, 21 December 2024 at 07:02:07 UTC, Steven Schveighoffer wrote:I wrote a blog post on how to use a single mixin to forward all operators to the D1 style overloads. You might find it useful or inspiring. https://www.schveiguy.com/blog/2022/06/how-to-keep-using-d1-operator-overloads/ -SteveThank you so much for posting this! This is exactly what I was hoping for. I saw that D used to have this other style of binops, but their state was unclear to me. This is a really cool collection of tricks and will definitely help me solve my current problem and keep learning cool D tricks. Cheers!
Dec 21