www.digitalmars.com         C & C++   DMDScript  

D - Free Operator Function Overloads

reply davepermen <davepermen_member pathlink.com> writes:
Walter

this is essencial. not only for streaming libraries, but simply in general.. an

Tc = Ta + Tb doesn't have ANY preference for any of those 3 types. it should be

Tc opAdd(Ta a,Tb b) {}

and not

Tc Ta.opAdd(Tb b) {}

or

Tc Tb.opAddR(Ta a) {}

or even worse, both!

just get rid of it. binary operators don't belong to a member. they never did.

and if you release that essencial missconcept, and replace it by the only real
one, we gain immediate access to a very fast implementable typesave, and
extendable streaming library..

Tc operator add(Ta a,Tb b) { }

would be my prefered way.
Jan 05 2004
next sibling parent reply Felix <Felix_member pathlink.com> writes:
Me too, I find a little weird the syntax Ta.opAdd(Tb) but, if I remember, this
is the standard way to overload operators in C++, too. Maybe, if cannot be
simply dropped, an alternative definition will be suitable.



In article <btbq8o$2g9e$1 digitaldaemon.com>, davepermen says...
Walter

this is essencial. not only for streaming libraries, but simply in general.. an

Tc = Ta + Tb doesn't have ANY preference for any of those 3 types. it should be

Tc opAdd(Ta a,Tb b) {}

and not

Tc Ta.opAdd(Tb b) {}

or

Tc Tb.opAddR(Ta a) {}

or even worse, both!

just get rid of it. binary operators don't belong to a member. they never did.

and if you release that essencial missconcept, and replace it by the only real
one, we gain immediate access to a very fast implementable typesave, and
extendable streaming library..

Tc operator add(Ta a,Tb b) { }

would be my prefered way.

Jan 05 2004
parent reply Antti =?iso-8859-1?Q?Syk=E4ri?= <jsykari gamma.hut.fi> writes:
In article <btbv9t$2p2s$1 digitaldaemon.com>, Felix wrote:
 Me too, I find a little weird the syntax Ta.opAdd(Tb) but, if I remember, this
 is the standard way to overload operators in C++, too. Maybe, if cannot be
 simply dropped, an alternative definition will be suitable.

There's no "standard" way in C++, you can pick any way you like. The things that affect your choice are the same with operators and other functions: - only member functions/operators can be overridden in a derived class - only non-member functions can be used to extend the class after it has been closed For example, suppose that the standard output mechanism in D would be something like Printer stdout; void main() { stdout ~ "Hello world " ~ 1 ~ 2 ~ 3 ~ endl; } And Printer implemented something like: class Printer { Printer opCat(int i) { .. } Printer opCat(char[] c) { .. } Printer opCat(Object o) { .. } } [replace Printer with OutputStream if you feel very generic] then the only way to introduce a new printable user-defined type, say Vector3, that isn't derived from Object but still can be printed to stdout is to make a free-standing function: Printer opCat(Printer p, Vector3 v) { p ~ "(" ~ v.x ~ ", " ~ v.y ~ ", " ~ v.z ~ ")"; } That is, if that was possible in D. You can do that in C++. If D was an OO only language, then making types not derived from Object would not be possible and the issue would be moot. Heck, we could be as well programming in Java. But I gather D is supposed be multiparadigm and all that, and you ought to be able to do stuff also without objects if you want. I object to objects. And if there is some template code somewhere, saying "stdout ~ t" where t is of type which is argument for the template, you sure as hell want your Vector3 to be printed instead of getting a message along the lines of "Printer does not have member function opCat(Printer, Vector3). Wanna change it? You can't! Bwahaha!". Duh! So to conclude the rant, I fully agree with davepermen:
 just get rid of it. binary operators don't belong to a member. they
 never did.

and Matthew:
 That's something we need that is supported by a large number of
 issues, not just this one. It's an absolute must, and I can't seee
 Walter escaping us on this one. :)

The only potential problems I see with free-standing operators are: 1. Importing or not importing a module containing operators will affect the way functions are overloaded. "a + b" might mean a different thing after adding an import. On the other hand, normal function calls are already vulnerable for this and I haven't experienced any problems whatsoever. Nor have I had any problems with the issue in C++, where it also exists. 2. The overloading rules might be a tad more complex with freestanding operators. That is, they might require some thinking so that they don't cause any surprises. Maybe there is someone less tired to perform the thinking ;) Complexity doesn't bother me if the rules are sufficiently simple to grasp intuitively. (Unless they end up in some Snake-Tongued Two-Phased Koenig Lookup or similar maze of mirrors) If there are any other valid reasons against non-member operators, please bring them forth because I for one have forgotten them. Did the original D "vision" include them? Why/why not? Is the reason that they haven't been implemented a reason of principle or merely a practical one? By tackling those issues one by one we might even arrive at a solution that satisfies everyone. At least, one can hope... -Antti
Jan 05 2004
next sibling parent reply "Ben Hinkle" <bhinkle4 juno.com> writes:
I agree it would probably be better to have binary overloading decoupled
from methods (trading off run-time dispatching against extending classes
without subclassing). But... from the D doc:
 "In D, function overloading is simple. It matches exactly, it matches with
implicit conversions, or it does not match. If there is more than one match,
it is an error."

So does that mean I can't specialize an existing function like:
Number opAdd(Number, int) {...}
NumberSubclass opAdd(NumberSubclass,int) {...}

If I have a variable of type NumberSubclass and I write "x+1" then it could
match both (one exactly and the other with an implicit conversion) and so
that would be an error. Or what if I had a subclass of NumberSubclass (so
there would be two implicit conversion matches).
It seems to me that this would make the proposed behavior of binary operator
overloading less useful. Not unusable, I suppose. It would worth looking at
a bunch of examples to see what would actually happen with either design and
what the advantages and disadvantages of each would be. I for one can't
immediately tell why one or the other would be a "no-brainer".

-Ben

"Antti Sykäri" <jsykari gamma.hut.fi> wrote in message
news:slrnbvjcbm.69b.jsykari pulu.hut.fi...
 In article <btbv9t$2p2s$1 digitaldaemon.com>, Felix wrote:
 Me too, I find a little weird the syntax Ta.opAdd(Tb) but, if I


 is the standard way to overload operators in C++, too. Maybe, if cannot


 simply dropped, an alternative definition will be suitable.

There's no "standard" way in C++, you can pick any way you like. The things that affect your choice are the same with operators and other functions: - only member functions/operators can be overridden in a derived class - only non-member functions can be used to extend the class after it has been closed For example, suppose that the standard output mechanism in D would be something like Printer stdout; void main() { stdout ~ "Hello world " ~ 1 ~ 2 ~ 3 ~ endl; } And Printer implemented something like: class Printer { Printer opCat(int i) { .. } Printer opCat(char[] c) { .. } Printer opCat(Object o) { .. } } [replace Printer with OutputStream if you feel very generic] then the only way to introduce a new printable user-defined type, say Vector3, that isn't derived from Object but still can be printed to stdout is to make a free-standing function: Printer opCat(Printer p, Vector3 v) { p ~ "(" ~ v.x ~ ", " ~ v.y ~ ", " ~ v.z ~ ")"; } That is, if that was possible in D. You can do that in C++. If D was an OO only language, then making types not derived from Object would not be possible and the issue would be moot. Heck, we could be as well programming in Java. But I gather D is supposed be multiparadigm and all that, and you ought to be able to do stuff also without objects if you want. I object to objects. And if there is some template code somewhere, saying "stdout ~ t" where t is of type which is argument for the template, you sure as hell want your Vector3 to be printed instead of getting a message along the lines of "Printer does not have member function opCat(Printer, Vector3). Wanna change it? You can't! Bwahaha!". Duh! So to conclude the rant, I fully agree with davepermen:
 just get rid of it. binary operators don't belong to a member. they
 never did.

and Matthew:
 That's something we need that is supported by a large number of
 issues, not just this one. It's an absolute must, and I can't seee
 Walter escaping us on this one. :)

The only potential problems I see with free-standing operators are: 1. Importing or not importing a module containing operators will affect the way functions are overloaded. "a + b" might mean a different thing after adding an import. On the other hand, normal function calls are already vulnerable for this and I haven't experienced any problems whatsoever. Nor have I had any problems with the issue in C++, where it also exists. 2. The overloading rules might be a tad more complex with freestanding operators. That is, they might require some thinking so that they don't cause any surprises. Maybe there is someone less tired to perform the thinking ;) Complexity doesn't bother me if the rules are sufficiently simple to grasp intuitively. (Unless they end up in some Snake-Tongued Two-Phased Koenig Lookup or similar maze of mirrors) If there are any other valid reasons against non-member operators, please bring them forth because I for one have forgotten them. Did the original D "vision" include them? Why/why not? Is the reason that they haven't been implemented a reason of principle or merely a practical one? By tackling those issues one by one we might even arrive at a solution that satisfies everyone. At least, one can hope... -Antti

Jan 05 2004
parent reply "Matthew" <matthew.hat stlsoft.dot.org> writes:
"Ben Hinkle" <bhinkle4 juno.com> wrote in message
news:btd7ks$1li5$1 digitaldaemon.com...
 I agree it would probably be better to have binary overloading decoupled
 from methods (trading off run-time dispatching against extending classes
 without subclassing). But... from the D doc:
  "In D, function overloading is simple. It matches exactly, it matches

 implicit conversions, or it does not match. If there is more than one

 it is an error."

 So does that mean I can't specialize an existing function like:
 Number opAdd(Number, int) {...}
 NumberSubclass opAdd(NumberSubclass,int) {...}

 If I have a variable of type NumberSubclass and I write "x+1" then it

 match both (one exactly and the other with an implicit conversion) and so
 that would be an error. Or what if I had a subclass of NumberSubclass (so
 there would be two implicit conversion matches).

I sincerely hope that the compiler could umabiguously match the second operator. If not, we're storing up a heap of trouble for later. Walter, can you clarify what will happen here?
 It seems to me that this would make the proposed behavior of binary

 overloading less useful. Not unusable, I suppose. It would worth looking

 a bunch of examples to see what would actually happen with either design

 what the advantages and disadvantages of each would be. I for one can't
 immediately tell why one or the other would be a "no-brainer".

 -Ben

 "Antti Sykäri" <jsykari gamma.hut.fi> wrote in message
 news:slrnbvjcbm.69b.jsykari pulu.hut.fi...
 In article <btbv9t$2p2s$1 digitaldaemon.com>, Felix wrote:
 Me too, I find a little weird the syntax Ta.opAdd(Tb) but, if I


 is the standard way to overload operators in C++, too. Maybe, if



 be
 simply dropped, an alternative definition will be suitable.

There's no "standard" way in C++, you can pick any way you like. The things that affect your choice are the same with operators and other functions: - only member functions/operators can be overridden in a derived class - only non-member functions can be used to extend the class after it has been closed For example, suppose that the standard output mechanism in D would be something like Printer stdout; void main() { stdout ~ "Hello world " ~ 1 ~ 2 ~ 3 ~ endl; } And Printer implemented something like: class Printer { Printer opCat(int i) { .. } Printer opCat(char[] c) { .. } Printer opCat(Object o) { .. } } [replace Printer with OutputStream if you feel very generic] then the only way to introduce a new printable user-defined type, say Vector3, that isn't derived from Object but still can be printed to stdout is to make a free-standing function: Printer opCat(Printer p, Vector3 v) { p ~ "(" ~ v.x ~ ", " ~ v.y ~ ", " ~ v.z ~ ")"; } That is, if that was possible in D. You can do that in C++. If D was an OO only language, then making types not derived from Object would not be possible and the issue would be moot. Heck, we could be as well programming in Java. But I gather D is supposed be multiparadigm and all that, and you ought to be able to do stuff also without objects if you want. I object to objects. And if there is some template code somewhere, saying "stdout ~ t" where t is of type which is argument for the template, you sure as hell want your Vector3 to be printed instead of getting a message along the lines of "Printer does not have member function opCat(Printer, Vector3). Wanna change it? You can't! Bwahaha!". Duh! So to conclude the rant, I fully agree with davepermen:
 just get rid of it. binary operators don't belong to a member. they
 never did.

and Matthew:
 That's something we need that is supported by a large number of
 issues, not just this one. It's an absolute must, and I can't seee
 Walter escaping us on this one. :)

The only potential problems I see with free-standing operators are: 1. Importing or not importing a module containing operators will affect the way functions are overloaded. "a + b" might mean a different thing after adding an import. On the other hand, normal function calls are already vulnerable for this and I haven't experienced any problems whatsoever. Nor have I had any problems with the issue in C++, where it also exists. 2. The overloading rules might be a tad more complex with freestanding operators. That is, they might require some thinking so that they don't cause any surprises. Maybe there is someone less tired to perform the thinking ;) Complexity doesn't bother me if the rules are sufficiently simple to grasp intuitively. (Unless they end up in some Snake-Tongued Two-Phased Koenig Lookup or similar maze of mirrors) If there are any other valid reasons against non-member operators, please bring them forth because I for one have forgotten them. Did the original D "vision" include them? Why/why not? Is the reason that they haven't been implemented a reason of principle or merely a practical one? By tackling those issues one by one we might even arrive at a solution that satisfies everyone. At least, one can hope... -Antti


Jan 05 2004
parent "Walter" <walter digitalmars.com> writes:
"Matthew" <matthew.hat stlsoft.dot.org> wrote in message
news:btd95n$1o1a$1 digitaldaemon.com...
 "Ben Hinkle" <bhinkle4 juno.com> wrote in message
 news:btd7ks$1li5$1 digitaldaemon.com...
 I agree it would probably be better to have binary overloading decoupled
 from methods (trading off run-time dispatching against extending classes
 without subclassing). But... from the D doc:
  "In D, function overloading is simple. It matches exactly, it matches

 implicit conversions, or it does not match. If there is more than one

 it is an error."

 So does that mean I can't specialize an existing function like:
 Number opAdd(Number, int) {...}
 NumberSubclass opAdd(NumberSubclass,int) {...}

 If I have a variable of type NumberSubclass and I write "x+1" then it

 match both (one exactly and the other with an implicit conversion) and


 that would be an error. Or what if I had a subclass of NumberSubclass


 there would be two implicit conversion matches).

I sincerely hope that the compiler could umabiguously match the second operator. If not, we're storing up a heap of trouble for later. Walter, can you clarify what will happen here?

It'll match the second, since it is an exact match. The first is a match with conversions.
Jan 06 2004
prev sibling next sibling parent reply "Walter" <walter digitalmars.com> writes:
"Antti Sykäri" <jsykari gamma.hut.fi> wrote in message
news:slrnbvjcbm.69b.jsykari pulu.hut.fi...
 If there are any other valid reasons against non-member operators,
 please bring them forth because I for one have forgotten them. Did the
 original D "vision" include them? Why/why not? Is the reason that they
 haven't been implemented a reason of principle or merely a practical
 one?

The problem is that if they were implemented, then there'd be a need for Koenig lookup. Once there's Koenig lookup, then there's the export lookup madness.
Jan 05 2004
next sibling parent reply "Matthew" <matthew.hat stlsoft.dot.org> writes:
"Walter" <walter digitalmars.com> wrote in message
news:btda5i$1puo$1 digitaldaemon.com...
 "Antti Sykäri" <jsykari gamma.hut.fi> wrote in message
 news:slrnbvjcbm.69b.jsykari pulu.hut.fi...
 If there are any other valid reasons against non-member operators,
 please bring them forth because I for one have forgotten them. Did the
 original D "vision" include them? Why/why not? Is the reason that they
 haven't been implemented a reason of principle or merely a practical
 one?

The problem is that if they were implemented, then there'd be a need for Koenig lookup.

With you so far
 Once there's Koenig lookup, then there's the export lookup
 madness.

Please explain
Jan 05 2004
parent reply "Walter" <walter digitalmars.com> writes:
"Matthew" <matthew.hat stlsoft.dot.org> wrote in message
news:btdalv$1qml$1 digitaldaemon.com...
 "Walter" <walter digitalmars.com> wrote in message
 news:btda5i$1puo$1 digitaldaemon.com...
 "Antti Sykäri" <jsykari gamma.hut.fi> wrote in message
 news:slrnbvjcbm.69b.jsykari pulu.hut.fi...
 If there are any other valid reasons against non-member operators,
 please bring them forth because I for one have forgotten them. Did the
 original D "vision" include them? Why/why not? Is the reason that they
 haven't been implemented a reason of principle or merely a practical
 one?

The problem is that if they were implemented, then there'd be a need for Koenig lookup.

With you so far
 Once there's Koenig lookup, then there's the export lookup
 madness.

Please explain

I've done some extensive googling for info on export. It seems the main difficulty with export is the ADL. The two phase lookup comes in to play, once when the template is defined and the other when it is instantiated. The second lookup is the ADL one. The ADL can cut across all of the 'translation units', meaning an arbitrarilly large number of separate symbol tables need to be analyzed. This is essentially madness. As best as I can tell, this problem was not realized when export was voted into the Standard, and is the source of many opinions that export is unimplementable. EDG proved it could be implemented, reportedly consuming 3 man years, but I don't hear any more about the alleged advantages of export (code hiding, faster compilation), and hence I suspect those advantages do not occur in practice. Export is a canonical example of how backwards compatibility with seemingly innocuous design decisions can lead to disaster. I do not wish to import that madness into D <g>.
Jan 06 2004
parent reply "Matthew" <matthew.hat stlsoft.dot.org> writes:
 "Matthew" <matthew.hat stlsoft.dot.org> wrote in message
 news:btdalv$1qml$1 digitaldaemon.com...
 "Walter" <walter digitalmars.com> wrote in message
 news:btda5i$1puo$1 digitaldaemon.com...
 "Antti Sykäri" <jsykari gamma.hut.fi> wrote in message
 news:slrnbvjcbm.69b.jsykari pulu.hut.fi...
 If there are any other valid reasons against non-member operators,
 please bring them forth because I for one have forgotten them. Did




 original D "vision" include them? Why/why not? Is the reason that




 haven't been implemented a reason of principle or merely a practical
 one?

The problem is that if they were implemented, then there'd be a need



 Koenig lookup.

With you so far
 Once there's Koenig lookup, then there's the export lookup
 madness.

Please explain

I've done some extensive googling for info on export. It seems the main difficulty with export is the ADL. The two phase lookup comes in to play, once when the template is defined and the other when it is instantiated.

 second lookup is the ADL one. The ADL can cut across all of the

 units', meaning an arbitrarilly large number of separate symbol tables

 to be analyzed. This is essentially madness.

 As best as I can tell, this problem was not realized when export was voted
 into the Standard, and is the source of many opinions that export is
 unimplementable. EDG proved it could be implemented, reportedly consuming

 man years, but I don't hear any more about the alleged advantages of

 (code hiding, faster compilation), and hence I suspect those advantages do
 not occur in practice.

 Export is a canonical example of how backwards compatibility with

 innocuous design decisions can lead to disaster.

 I do not wish to import that madness into D <g>.

I've not given it enough thought on a global scale, but I can't see how you can expect to get away without ADL, with functions and non-member operators. If this means you must go for export madness, then you'd better get out the white coat.
Jan 06 2004
parent reply "Walter" <walter digitalmars.com> writes:
"Matthew" <matthew.hat stlsoft.dot.org> wrote in message
news:bte1ib$2v71$1 digitaldaemon.com...
 I've not given it enough thought on a global scale, but I can't see how

 can expect to get away without ADL, with functions and non-member

ADL is justified by non-member operators. Without non-member operators, no ADL is needed.
 If this means you must go for export madness, then you'd better get out

 white coat.

Jan 06 2004
parent reply "Matthew" <matthew.hat stlsoft.dot.org> writes:
"Walter" <walter digitalmars.com> wrote in message
news:bte342$31cg$1 digitaldaemon.com...
 "Matthew" <matthew.hat stlsoft.dot.org> wrote in message
 news:bte1ib$2v71$1 digitaldaemon.com...
 I've not given it enough thought on a global scale, but I can't see how

 can expect to get away without ADL, with functions and non-member

ADL is justified by non-member operators. Without non-member operators, no ADL is needed.

Not true. Generalising shims require ADL for a kick off.
Jan 06 2004
parent "Walter" <walter digitalmars.com> writes:
"Matthew" <matthew.hat stlsoft.dot.org> wrote in message
news:bteaea$akr$1 digitaldaemon.com...
 Not true. Generalising shims require ADL for a kick off.

Example?
Jan 06 2004
prev sibling parent davepermen <davepermen_member pathlink.com> writes:
dunno.. i thought thats only for namespaces..

In article <btda5i$1puo$1 digitaldaemon.com>, Walter says...
"Antti Sykäri" <jsykari gamma.hut.fi> wrote in message
news:slrnbvjcbm.69b.jsykari pulu.hut.fi...
 If there are any other valid reasons against non-member operators,
 please bring them forth because I for one have forgotten them. Did the
 original D "vision" include them? Why/why not? Is the reason that they
 haven't been implemented a reason of principle or merely a practical
 one?

The problem is that if they were implemented, then there'd be a need for Koenig lookup. Once there's Koenig lookup, then there's the export lookup madness.

Jan 06 2004
prev sibling parent reply davepermen <davepermen_member pathlink.com> writes:
i don't see how the operator overloading can result in more work to "look up"..

if the parser finds some code snipped like this:

a + b

then it simply translates it to

opAdd(a,b)

directly. and then its just a normal function call.. and done. the normal
operator rules for encoding/parsing code-lines with operators still apply, and
the endresult is pleasing, and with no issues in parsing at all..

class Printer {
Printer print(char[] text) { ...; return this; }
}

Printer opStream(Printer p,char[] text) { return p.print(text); }
Printer opStream(Printer p,int value) { return p.print(toString(value)); }

say opStream is <~ (my lovely token:D)..

then you can write

Printer opStream(Printer p,vec3 v) {
p <~ '(' <~ x <~ ',' <~ y <~ ',' <~ z <~ ')';
}

and a simple piece of code would be like this:

Printer p(stdout);

p <~ "Position: " <~ position;

wich would translate to

opStream(opStream(p,"Position: "),position);

and would then simply decode to

opStream(opStream(Printer,char[]),vec3);

for now, it would be doable with opCall.. (as yet suggested by some).

opStream could get some syntactic sugar, like we don't need to write

StreamerType opStream(StreamerType s,T) {
/* access stream */
return s;
}

but only

char[] opStream(T) {
/* access stream */
return streamlined data packet; // doesn't have to be char[], but in case of io,
you stream with char[] often
}

and

class StreamerType {
opStream(char[] s) {
/* print stream */
}
}


example:

class Printer {
opStream(char[] s) {
printf("%.*s",s);
}
}

char[] opStream(char[] text) {
return text; // just rewrite it to the stream as its the base format.
}

char[] opStream(vec3) {
return <~'('<~x<~','<~y<~','<~z<~')';
}

Printer p;

p <~ "Position: " <~ position;

would translate to

p.opStream(opStream(text) ~ opStream(position));

this thought is not finished, but merely an idea..

but overloadable non-member operators are ESSENCIAL. and i don't see why they
are, at all, different, than what we have now. technically.

In article <slrnbvjcbm.69b.jsykari pulu.hut.fi>, Antti =?iso-8859-1?Q?Syk=E4ri?=
says...
In article <btbv9t$2p2s$1 digitaldaemon.com>, Felix wrote:
 Me too, I find a little weird the syntax Ta.opAdd(Tb) but, if I remember, this
 is the standard way to overload operators in C++, too. Maybe, if cannot be
 simply dropped, an alternative definition will be suitable.

There's no "standard" way in C++, you can pick any way you like. The things that affect your choice are the same with operators and other functions: - only member functions/operators can be overridden in a derived class - only non-member functions can be used to extend the class after it has been closed For example, suppose that the standard output mechanism in D would be something like Printer stdout; void main() { stdout ~ "Hello world " ~ 1 ~ 2 ~ 3 ~ endl; } And Printer implemented something like: class Printer { Printer opCat(int i) { .. } Printer opCat(char[] c) { .. } Printer opCat(Object o) { .. } } [replace Printer with OutputStream if you feel very generic] then the only way to introduce a new printable user-defined type, say Vector3, that isn't derived from Object but still can be printed to stdout is to make a free-standing function: Printer opCat(Printer p, Vector3 v) { p ~ "(" ~ v.x ~ ", " ~ v.y ~ ", " ~ v.z ~ ")"; } That is, if that was possible in D. You can do that in C++. If D was an OO only language, then making types not derived from Object would not be possible and the issue would be moot. Heck, we could be as well programming in Java. But I gather D is supposed be multiparadigm and all that, and you ought to be able to do stuff also without objects if you want. I object to objects. And if there is some template code somewhere, saying "stdout ~ t" where t is of type which is argument for the template, you sure as hell want your Vector3 to be printed instead of getting a message along the lines of "Printer does not have member function opCat(Printer, Vector3). Wanna change it? You can't! Bwahaha!". Duh! So to conclude the rant, I fully agree with davepermen:
 just get rid of it. binary operators don't belong to a member. they
 never did.

and Matthew:
 That's something we need that is supported by a large number of
 issues, not just this one. It's an absolute must, and I can't seee
 Walter escaping us on this one. :)

The only potential problems I see with free-standing operators are: 1. Importing or not importing a module containing operators will affect the way functions are overloaded. "a + b" might mean a different thing after adding an import. On the other hand, normal function calls are already vulnerable for this and I haven't experienced any problems whatsoever. Nor have I had any problems with the issue in C++, where it also exists. 2. The overloading rules might be a tad more complex with freestanding operators. That is, they might require some thinking so that they don't cause any surprises. Maybe there is someone less tired to perform the thinking ;) Complexity doesn't bother me if the rules are sufficiently simple to grasp intuitively. (Unless they end up in some Snake-Tongued Two-Phased Koenig Lookup or similar maze of mirrors) If there are any other valid reasons against non-member operators, please bring them forth because I for one have forgotten them. Did the original D "vision" include them? Why/why not? Is the reason that they haven't been implemented a reason of principle or merely a practical one? By tackling those issues one by one we might even arrive at a solution that satisfies everyone. At least, one can hope... -Antti

Jan 06 2004
parent reply "Walter" <walter digitalmars.com> writes:
"davepermen" <davepermen_member pathlink.com> wrote in message
news:bte3p3$pp$1 digitaldaemon.com...
 i don't see how the operator overloading can result in more work to "look

 if the parser finds some code snipped like this:

 a + b

 then it simply translates it to

 opAdd(a,b)

 directly. and then its just a normal function call.. and done. the normal
 operator rules for encoding/parsing code-lines with operators still apply,

 the endresult is pleasing, and with no issues in parsing at all..

The problem is if opAdd() is defined in another module that is not the current module. It will not be found with the normal scoped lookup. With ordinary functions, one just does: foo.opAdd(a,b) if the function is in module foo. That doesn't work out so good for operator overloads. What ADL does is look at the types of the arguments to the function, and uses those types to add more scopes to look for the opAdd in.
Jan 06 2004
next sibling parent reply Hauke Duden <H.NS.Duden gmx.net> writes:
Walter wrote:
 The problem is if opAdd() is defined in another module that is not the
 current module. It will not be found with the normal scoped lookup. With
 ordinary functions, one just does:
 
     foo.opAdd(a,b)
 
 if the function is in module foo. That doesn't work out so good for operator
 overloads. What ADL does is look at the types of the arguments to the
 function, and uses those types to add more scopes to look for the opAdd in.

Would that even be sufficient? Doesn't one expect to be able to write an opAdd that works on objects whose classes are defined in other modules? I.e. what if the opAdd is in neither of the object modules, but in a completely different one? Seems to me that the only sensible solution is to require that the module opAdd is defined in has to be imported for it to be used. Apply the same rules as in overloaded function calling - it IS only syntactic sugar, after all. import math.vectors; import gnu.bignum; import bigNumIntegration; //defines opAdd(Vector,BigNum) Vector v; BigNum b; v + b; //works only if fancyOperatorModule is imported And if a compatible operator is defined in more than one imported module then a qualified call of the type bigNumIntegration.opAdd(v,b) instead of (v + b) can be used. Those cases should be rare, so no big deal. Hauke
Jan 06 2004
next sibling parent Hauke Duden <H.NS.Duden gmx.net> writes:
Hauke Duden wrote:
 v + b;    //works only if fancyOperatorModule is imported

Sorry, that should have been v + b; //works only if bigNumIntegration is imported
Jan 06 2004
prev sibling next sibling parent reply "Matthew" <matthew.hat stlsoft.dot.org> writes:
 Walter wrote:
 The problem is if opAdd() is defined in another module that is not the
 current module. It will not be found with the normal scoped lookup. With
 ordinary functions, one just does:

     foo.opAdd(a,b)

 if the function is in module foo. That doesn't work out so good for


 overloads. What ADL does is look at the types of the arguments to the
 function, and uses those types to add more scopes to look for the opAdd


 Would that even be sufficient? Doesn't one expect to be able to write an
 opAdd that works on objects whose classes are defined in other modules?

Absolutely. And without which, we'll all be hitting ourselves in the head and turning for the C++ compiler.
 I.e. what if the opAdd is in neither of the object modules, but in a
 completely different one?

 Seems to me that the only sensible solution is to require that the
 module opAdd is defined in has to be imported for it to be used. Apply
 the same rules as in overloaded function calling - it IS only syntactic
 sugar, after all.

But what about when you're working with permutations of all kinds of types from potentially unlimited modules, in generic code? D wants to be generic. Therefore D needs ADL. If ADL is hard for compilers, so be it. Those appear to be the breaks.
Jan 06 2004
parent Hauke Duden <H.NS.Duden gmx.net> writes:
Matthew wrote:
I.e. what if the opAdd is in neither of the object modules, but in a
completely different one?

Seems to me that the only sensible solution is to require that the
module opAdd is defined in has to be imported for it to be used. Apply
the same rules as in overloaded function calling - it IS only syntactic
sugar, after all.

But what about when you're working with permutations of all kinds of types from potentially unlimited modules, in generic code? D wants to be generic. Therefore D needs ADL. If ADL is hard for compilers, so be it. Those appear to be the breaks.

I have no idea what ADL is, but wouldn't the same problem also apply to all other overloaded global functions? I think maybe I understand the problem now. Please correct me if I'm wrong: If you write a template that contains a function call to foo(A a,B b), where A and B are template parameters, then this template will be instantiated only once for each combination of (A,B) it is used with. The problem is that if you want to overload foo in a module unrelated to A and B, then the template instance becomes dependent not only on the types A and B, but also on the module where foo is defined. But THAT may be different, depending on where the template is actually used, since different places of usage may import different modules. I do not see how this problem can ever be solved, except by treating templates like macros and simply always compiling it in the context it is used in, every time. But if this isn't solved for "normal" global functions, why does it have to be solved for global operators? Besides, I think the problem should be pretty rare in practice. The only situation where it would cause trouble is if the same template is used with the same two types at two different places in the program, and you have imported a different definition of the operator/global function in one context than in the other. In the case of stream operators and stuff like that, I think that you'll usually have at most one overload for each combination of types. So, can't the calls of global functions/operators inside templates be resolved from the context where the template is instantiated first? That seems to be the only workable alternative to me. Hauke
Jan 06 2004
prev sibling next sibling parent "Ben Hinkle" <bhinkle4 juno.com> writes:
"Hauke Duden" <H.NS.Duden gmx.net> wrote in message
news:bte5b4$3cq$1 digitaldaemon.com...
 Walter wrote:
 The problem is if opAdd() is defined in another module that is not the
 current module. It will not be found with the normal scoped lookup. With
 ordinary functions, one just does:

     foo.opAdd(a,b)

 if the function is in module foo. That doesn't work out so good for


 overloads. What ADL does is look at the types of the arguments to the
 function, and uses those types to add more scopes to look for the opAdd


 Would that even be sufficient? Doesn't one expect to be able to write an
 opAdd that works on objects whose classes are defined in other modules?

Can't the function dispatch a method call by hand? For example Number opAdd(Number a, Number2 b) { return b.opAdd(a); } Then it is up to Number2 (or a subclass of Number2) to define opAdd. It would be nice to have the compiler's ability to detect if a method exists of a given signature before calling it, though, which would mean some reflection support. I would definitely want to keep all function resolution to things in scope and leave unscoped function resolution to run-time dispatching. That seems the only sane way to go.
 I.e. what if the opAdd is in neither of the object modules, but in a
 completely different one?

 Seems to me that the only sensible solution is to require that the
 module opAdd is defined in has to be imported for it to be used. Apply
 the same rules as in overloaded function calling - it IS only syntactic
 sugar, after all.

 import math.vectors;
 import gnu.bignum;
 import bigNumIntegration; //defines opAdd(Vector,BigNum)

 Vector v;
 BigNum b;

 v + b; //works only if fancyOperatorModule is imported

 And if a compatible operator is defined in more than one imported module
 then a qualified call of the type bigNumIntegration.opAdd(v,b) instead
 of (v + b) can be used. Those cases should be rare, so no big deal.


 Hauke

Jan 06 2004
prev sibling parent reply "Walter" <walter digitalmars.com> writes:
"Hauke Duden" <H.NS.Duden gmx.net> wrote in message
news:bte5b4$3cq$1 digitaldaemon.com...
 Walter wrote:
 The problem is if opAdd() is defined in another module that is not the
 current module. It will not be found with the normal scoped lookup. With
 ordinary functions, one just does:

     foo.opAdd(a,b)

 if the function is in module foo. That doesn't work out so good for


 overloads. What ADL does is look at the types of the arguments to the
 function, and uses those types to add more scopes to look for the opAdd


 Would that even be sufficient? Doesn't one expect to be able to write an
 opAdd that works on objects whose classes are defined in other modules?

Of course. But how member operator lookup works is: a + b If a is an instance of Foo, then Foo.opAdd(b) is looked for. Foo can be in another module. No special ADL rule is required.
Jan 06 2004
next sibling parent reply "Carlos Santander B." <carlos8294 msn.com> writes:
"Walter" <walter digitalmars.com> wrote in message
news:btf3no$1hk9$2 digitaldaemon.com...
|
|
| Of course. But how member operator lookup works is:
|
|     a + b
|
| If a is an instance of Foo, then Foo.opAdd(b) is looked for. Foo can be in
| another module. No special ADL rule is required.
|
|

What if all operators can be reversed? That way, if there's no
Foo.opAdd(typeof(b)), the compiler will look for typeof(b).opAdd_r(Foo).
Also, those who want to possibly extend a stream class, would do
MyType.opAdd_r(Stream). What about that?

-----------------------
Carlos Santander Bernal
Jan 06 2004
next sibling parent reply "Walter" <walter digitalmars.com> writes:
"Carlos Santander B." <carlos8294 msn.com> wrote in message
news:btfh6d$26rc$1 digitaldaemon.com...
 "Walter" <walter digitalmars.com> wrote in message
 news:btf3no$1hk9$2 digitaldaemon.com...
 |
 |
 | Of course. But how member operator lookup works is:
 |
 |     a + b
 |
 | If a is an instance of Foo, then Foo.opAdd(b) is looked for. Foo can be

 | another module. No special ADL rule is required.
 |
 |

 What if all operators can be reversed? That way, if there's no
 Foo.opAdd(typeof(b)), the compiler will look for typeof(b).opAdd_r(Foo).

Since add is commutative, the compiler will first look for typeof(a).opAdd, if that isn't found, it will look for typeof(b).opAdd.
 Also, those who want to possibly extend a stream class, would do
 MyType.opAdd_r(Stream). What about that?

I philosophically object to using operator overloading for stream I/O <g>.
Jan 06 2004
next sibling parent reply davepermen <davepermen_member pathlink.com> writes:
what bether thing do you suggest then? with the operators, we get a nice simple
syntax to the way it has to be: for every type-part of the stream, we have an
"addObjToStream" function that gets called, and does exactly the needed work.

D is compile time typed, as is c++. so everything about the type determination
should be compile time. only the needed part, a.k.a. the actual conversion of a
type to the streamformat, should be runtime (if needed:D). and a direct function
per type (and streamtype) would work perfect. not any more runtime overhead than
needed.

but

write(stream,"Hello, my name is ");
write(stream,name);

is clumpsy (while exactly doable currently that way).

and that is why operator overloading would fit in that perfectly. it makes it
possible to chain a lot of such write calls into one rather easy to read line.

if you can think logically, you would not try to judge phylosophically about it.

c++ provides a solution that is typesave, very simple extendable, working with
existing language features, is simple, and can be very fast. the c++ version
possibly isn't. but the D version can.

In article <btfjb9$2a4k$1 digitaldaemon.com>, Walter says...
"Carlos Santander B." <carlos8294 msn.com> wrote in message
news:btfh6d$26rc$1 digitaldaemon.com...
 "Walter" <walter digitalmars.com> wrote in message
 news:btf3no$1hk9$2 digitaldaemon.com...
 |
 |
 | Of course. But how member operator lookup works is:
 |
 |     a + b
 |
 | If a is an instance of Foo, then Foo.opAdd(b) is looked for. Foo can be

 | another module. No special ADL rule is required.
 |
 |

 What if all operators can be reversed? That way, if there's no
 Foo.opAdd(typeof(b)), the compiler will look for typeof(b).opAdd_r(Foo).

Since add is commutative, the compiler will first look for typeof(a).opAdd, if that isn't found, it will look for typeof(b).opAdd.
 Also, those who want to possibly extend a stream class, would do
 MyType.opAdd_r(Stream). What about that?

I philosophically object to using operator overloading for stream I/O <g>.

Jan 07 2004
next sibling parent reply "Walter" <walter digitalmars.com> writes:
"davepermen" <davepermen_member pathlink.com> wrote in message
news:btgjga$skd$1 digitaldaemon.com...
 what bether thing do you suggest then? with the operators, we get a nice

 syntax to the way it has to be: for every type-part of the stream, we have

 "addObjToStream" function that gets called, and does exactly the needed

There was an earlier thread about overloading () to implement stream I/O, it works nicely and does not require ADL. It winds up looking like: stdout(arg1)(arg2)(arg3) instead of (in C++): stdout << arg1 << arg2 << arg3 and besides, ADL isn't necessary for the latter either if << is overloaded as a member of stdout.
Jan 07 2004
parent reply davepermen <davepermen_member pathlink.com> writes:
i do understand that. but you simply can't overload your prefered operator for
ANY POSSIBLE TYPE EVER EXISTING. this is just not possible.

free overloadable functions give the ability to add overloads to your stream
without any rewriting of stream or type.

THAT is why i want free operators. they are the way operators behave, and they
are the only way to make streams fast extendable and type save. and FAST.
everyone cries that they have to be faster than the c++ streams. believe me they
can be hell fast. and i can for sure overload all sort of types for my stdout
class and then write stdout << a << b << c; but this is nost scalable.

a library ALWAYS has to provide features a user can use for ANY situation. that
means he has to have a nice plugin-interface. overloadable free operators are a
GREAT plugin-interface for streams.

the by far best.

In article <btglpg$100p$1 digitaldaemon.com>, Walter says...
"davepermen" <davepermen_member pathlink.com> wrote in message
news:btgjga$skd$1 digitaldaemon.com...
 what bether thing do you suggest then? with the operators, we get a nice

 syntax to the way it has to be: for every type-part of the stream, we have

 "addObjToStream" function that gets called, and does exactly the needed

There was an earlier thread about overloading () to implement stream I/O, it works nicely and does not require ADL. It winds up looking like: stdout(arg1)(arg2)(arg3) instead of (in C++): stdout << arg1 << arg2 << arg3 and besides, ADL isn't necessary for the latter either if << is overloaded as a member of stdout.

Jan 07 2004
parent reply "Walter" <walter digitalmars.com> writes:
"davepermen" <davepermen_member pathlink.com> wrote in message
news:bthndh$2jdb$1 digitaldaemon.com...
 i do understand that. but you simply can't overload your prefered operator

 ANY POSSIBLE TYPE EVER EXISTING. this is just not possible.
 free overloadable functions give the ability to add overloads to your

 without any rewriting of stream or type.

 THAT is why i want free operators. they are the way operators behave, and

 are the only way to make streams fast extendable and type save. and FAST.
 everyone cries that they have to be faster than the c++ streams. believe

 can be hell fast. and i can for sure overload all sort of types for my

 class and then write stdout << a << b << c; but this is nost scalable.

 a library ALWAYS has to provide features a user can use for ANY situation.

 means he has to have a nice plugin-interface. overloadable free operators

 GREAT plugin-interface for streams.

I understand your point. But there's got to be a better way than ADL.
Jan 09 2004
parent reply davepermen <davepermen_member pathlink.com> writes:
operators have to be in the module you use them (sort of private operators), or
in one of the modules of one of the used types. just as overloadable functions,
more or less..

on the other hand.. the

print(..) { /+ defines this is chainable +/ } // only two dots, like in [x..y]
print(int x) { printf("%i",x); }
print(char[] x) { printf("%.*s",x); }
print(vec3 x) { printf("(%f,%f,%f)",x.x,x.y,x.z); }

etc

wich concatenates

print("Hello World, my Name is ",davepermen," I'm ",19," and i life at
",position);

wich will then map to
print(char[]); // print("Hello World, my Name is ");
print(char[]); // print(davepermen);
print(char[]); // print(" I'm ");
print(int);    // print(19);
print(char[]); // print(" and i life at ");
print(vec3);   // print(position);

and if this is too implicit, possibly we have to call the function more like
this:

print..("Hello World, my Name is ",davepermen," I'm ",19," and i life at
",position); // note the two dots..

or even

print("Hello World, my Name is "..davepermen.." I'm "..19.." and i life at
"..position); // hm, a real stream-chain operator

how to mix that with oo? dunno.. :|

anyways.. lot to think about.. how to make the language flexible extendable
without issues for the compiler?

one word, walter: shit

:D at least you see my points as well.. now lets find some way to work this out.
and then, syntax-sugar it as much as possible:D

In article <btlq6k$2obr$1 digitaldaemon.com>, Walter says...
"davepermen" <davepermen_member pathlink.com> wrote in message
news:bthndh$2jdb$1 digitaldaemon.com...
 i do understand that. but you simply can't overload your prefered operator

 ANY POSSIBLE TYPE EVER EXISTING. this is just not possible.
 free overloadable functions give the ability to add overloads to your

 without any rewriting of stream or type.

 THAT is why i want free operators. they are the way operators behave, and

 are the only way to make streams fast extendable and type save. and FAST.
 everyone cries that they have to be faster than the c++ streams. believe

 can be hell fast. and i can for sure overload all sort of types for my

 class and then write stdout << a << b << c; but this is nost scalable.

 a library ALWAYS has to provide features a user can use for ANY situation.

 means he has to have a nice plugin-interface. overloadable free operators

 GREAT plugin-interface for streams.

I understand your point. But there's got to be a better way than ADL.

Jan 09 2004
parent "Sean L. Palmer" <palmer.sean verizon.net> writes:
Streams are another kind of iterator (input, or output).

It's also another one of those situations that must be extended to any type,
and where the code that controls the iteration (exactly what to print) is
typically written well after the code that deals with each iteration (how to
print each kind of thing) which is written well after the buffering code.
But the later code can't be fast unless the buffering code is spread out
throughout the outer function calls (heavily inlined to the point where the
generated code is just writing bytes directly where they go in the stream
buffer, advancing the buffer pointer or calling to allocate more memory
periodically).

This is hard.  It needs some kind of code injection that you can do in C++
by class wrapping, but not so easily in D (no real dtors, ill-defined order
of shutdown, etc).

Anyway we need to be able to extend it in quite a few different ways.  We
need to be able to add new types that can be input or output, or saved and
loaded, we need to be able to add new types that can act as streams, and
hopefully integrate with the container classes somehow to the point where
you could say:

sys.outstream s;
int c[100];
s.print(c[]);

or, better, something like:

s.print(intersperse(c[], ','));

Sean

"davepermen" <davepermen_member pathlink.com> wrote in message
news:btlt7b$2t6n$1 digitaldaemon.com...
 operators have to be in the module you use them (sort of private

 in one of the modules of one of the used types. just as overloadable

 more or less..

 on the other hand.. the

 print(..) { /+ defines this is chainable +/ } // only two dots, like in

 print(int x) { printf("%i",x); }
 print(char[] x) { printf("%.*s",x); }
 print(vec3 x) { printf("(%f,%f,%f)",x.x,x.y,x.z); }

 etc

 wich concatenates

 print("Hello World, my Name is ",davepermen," I'm ",19," and i life at
 ",position);

 wich will then map to
 print(char[]); // print("Hello World, my Name is ");
 print(char[]); // print(davepermen);
 print(char[]); // print(" I'm ");
 print(int);    // print(19);
 print(char[]); // print(" and i life at ");
 print(vec3);   // print(position);

 and if this is too implicit, possibly we have to call the function more

 this:

 print..("Hello World, my Name is ",davepermen," I'm ",19," and i life at
 ",position); // note the two dots..

 or even

 print("Hello World, my Name is "..davepermen.." I'm "..19.." and i life at
 "..position); // hm, a real stream-chain operator

 how to mix that with oo? dunno.. :|

 anyways.. lot to think about.. how to make the language flexible

 without issues for the compiler?

 one word, walter: shit

 :D at least you see my points as well.. now lets find some way to work

 and then, syntax-sugar it as much as possible:D

 In article <btlq6k$2obr$1 digitaldaemon.com>, Walter says...
"davepermen" <davepermen_member pathlink.com> wrote in message
news:bthndh$2jdb$1 digitaldaemon.com...
 i do understand that. but you simply can't overload your prefered



for
 ANY POSSIBLE TYPE EVER EXISTING. this is just not possible.
 free overloadable functions give the ability to add overloads to your

 without any rewriting of stream or type.

 THAT is why i want free operators. they are the way operators behave,



they
 are the only way to make streams fast extendable and type save. and



 everyone cries that they have to be faster than the c++ streams.



me they
 can be hell fast. and i can for sure overload all sort of types for my

 class and then write stdout << a << b << c; but this is nost scalable.

 a library ALWAYS has to provide features a user can use for ANY



that
 means he has to have a nice plugin-interface. overloadable free



are a
 GREAT plugin-interface for streams.

I understand your point. But there's got to be a better way than ADL.


Jan 11 2004
prev sibling parent reply Andy Friesen <andy ikagames.com> writes:
davepermen wrote:
 what bether thing do you suggest then? with the operators, we get a nice simple
 syntax to the way it has to be: for every type-part of the stream, we have an
 "addObjToStream" function that gets called, and does exactly the needed work.
 
 D is compile time typed, as is c++. so everything about the type determination
 should be compile time. only the needed part, a.k.a. the actual conversion of a
 type to the streamformat, should be runtime (if needed:D). and a direct
function
 per type (and streamtype) would work perfect. not any more runtime overhead
than
 needed.
 
 but
 
 write(stream,"Hello, my name is ");
 write(stream,name);
 
 is clumpsy (while exactly doable currently that way).
 
 and that is why operator overloading would fit in that perfectly. it makes it
 possible to chain a lot of such write calls into one rather easy to read line.
 
 if you can think logically, you would not try to judge phylosophically about
it.
 
 c++ provides a solution that is typesave, very simple extendable, working with
 existing language features, is simple, and can be very fast. the c++ version
 possibly isn't. but the D version can.

It's an irk. I don't see any way to work around it within the confines of D without resorting to polymorphism. The question is merely whether polymorphism is a good way to deal with this. Given that we're talking about something that would (certainly should) be in the standard library, I don't see why not. Making streams deal with PDTs is trivial enough, since they are defined by the compiler, not the code. Dealing with objects is similarly easy: define a Streamable interface, or just use Object.toString(). Structs require a few tiny hoops, but even that's pretty easy: class StreamWrap(T) : IStreamable { this(T t) { _t = t; } void toStream(Stream stream) { stream.write(_t.toStream()); } } The template could further be specialized for reference types and PDTs to provide genericity. Free operator functions would be nice, but it doesn't seem to be the only good way to handle the streaming issue. -- andy
Jan 07 2004
parent reply davepermen <davepermen_member pathlink.com> writes:
but the by far most straightforward and logical way. you don't need delegates as
well, you can do that with a library-implementation. see boost::function.

the trick is, with operator overloading as free functions we get much more
flexibility than only for streams. but there its most obvious. it will be as
fast as directly setting up the specific code for your specific output stream
format manually. (the chain of write(stream,T) calls), wich is the fastest way.
in every other situation, we have to rely on compiler optimisations to _KNOW_
its as fast.

if you don't see that, you don't see encapsulation as something important, then?
because operators don't belong to types, they should never be in their
interface.

In article <bthkn2$2eq0$1 digitaldaemon.com>, Andy Friesen says...
davepermen wrote:
 what bether thing do you suggest then? with the operators, we get a nice simple
 syntax to the way it has to be: for every type-part of the stream, we have an
 "addObjToStream" function that gets called, and does exactly the needed work.
 
 D is compile time typed, as is c++. so everything about the type determination
 should be compile time. only the needed part, a.k.a. the actual conversion of a
 type to the streamformat, should be runtime (if needed:D). and a direct
function
 per type (and streamtype) would work perfect. not any more runtime overhead
than
 needed.
 
 but
 
 write(stream,"Hello, my name is ");
 write(stream,name);
 
 is clumpsy (while exactly doable currently that way).
 
 and that is why operator overloading would fit in that perfectly. it makes it
 possible to chain a lot of such write calls into one rather easy to read line.
 
 if you can think logically, you would not try to judge phylosophically about
it.
 
 c++ provides a solution that is typesave, very simple extendable, working with
 existing language features, is simple, and can be very fast. the c++ version
 possibly isn't. but the D version can.

It's an irk. I don't see any way to work around it within the confines of D without resorting to polymorphism. The question is merely whether polymorphism is a good way to deal with this. Given that we're talking about something that would (certainly should) be in the standard library, I don't see why not. Making streams deal with PDTs is trivial enough, since they are defined by the compiler, not the code. Dealing with objects is similarly easy: define a Streamable interface, or just use Object.toString(). Structs require a few tiny hoops, but even that's pretty easy: class StreamWrap(T) : IStreamable { this(T t) { _t = t; } void toStream(Stream stream) { stream.write(_t.toStream()); } } The template could further be specialized for reference types and PDTs to provide genericity. Free operator functions would be nice, but it doesn't seem to be the only good way to handle the streaming issue. -- andy

Jan 07 2004
next sibling parent reply Andy Friesen <andy ikagames.com> writes:
davepermen wrote:
 but the by far most straightforward and logical way. you don't need delegates
as
 well, you can do that with a library-implementation. see boost::function.

There are lots of reasons not to use boost. It's big and monolithic and doesn't nicely fit into other build systems. Blech. boost::function has an inordinately convoluted implementation as well. (which includes some impressively obfuscated preprocessor hackery, and just plain brute forced template declarations for dealing with up to 50 arguments)
 the trick is, with operator overloading as free functions we get much more
 flexibility than only for streams. but there its most obvious. it will be as
 fast as directly setting up the specific code for your specific output stream
 format manually. (the chain of write(stream,T) calls), wich is the fastest way.
 in every other situation, we have to rely on compiler optimisations to _KNOW_
 its as fast.
 
 if you don't see that, you don't see encapsulation as something important,
then?
 because operators don't belong to types, they should never be in their
 interface.

I'd like to see proof that one more virtual call per nonPDT element being written is going to be unaccetably slow before agreeing that it's the only way. Premature optimization and all that. Fast is good, but one of the main goals of D is to keep Walter from having to turn his head inside out making the compiler work right. Simple compilers also tend to be fast compilers. I'm willing to give a few inches on this subject. As for encapsulation, I do believe that nonmember functions are more encapsulated than member functions, since they are forced to rely on the public interface, and are therefore immune to implementation fluxuations. Operator overloads are a small enough minority that I don't see this as being a particularly important issue. There are far more methods that are not overloading an operator than are. -- andy
Jan 07 2004
parent davepermen <davepermen_member pathlink.com> writes:
davepermen wrote:
 but the by far most straightforward and logical way. you don't need delegates
as
 well, you can do that with a library-implementation. see boost::function.

There are lots of reasons not to use boost. It's big and monolithic and doesn't nicely fit into other build systems. Blech. boost::function has an inordinately convoluted implementation as well. (which includes some impressively obfuscated preprocessor hackery, and just plain brute forced template declarations for dealing with up to 50 arguments)

this was sarcasm, dude! the hackery boost has to do to implement rather good features into c++ is impressive (boost itself is an amazing piece of code! nothing against that). but boost::function can not in any form replace a simple delegate as in D
Fast is good, but one of the main goals of D is to keep Walter from 
having to turn his head inside out making the compiler work right. 
Simple compilers also tend to be fast compilers.  I'm willing to give a 
few inches on this subject.

theoretically very simple way, to accomplish an in general very fast and proper solution by default, makes me believe its worth fighting for it. i see the issues, but it doesn't remove the fact, that the way c++ allows to write operators give us a great way to make it work without virtual functions.
As for encapsulation, I do believe that nonmember functions are more 
encapsulated than member functions, since they are forced to rely on the 
public interface, and are therefore immune to implementation 
fluxuations.

perator overloads are a small enough minority that I 
don't see this as being a particularly important issue.  There are far 
more methods that are not overloading an operator than are.

access inner parts of an object.. but if its really not possible for walter to find a way he likes it, then its okay.. i still don't see any reason for it. operators and functions are in my eyes, 100% interchangeable. if you can't call a function without specifiyng the package, then you simply can't use such an operator, too.. wouldn't hurt ME at all. as they are just sugar, as i said, yet.
Jan 07 2004
prev sibling parent "Matthew" <matthew.hat stlsoft.dot.org> writes:
<snip>

 if you don't see that, you don't see encapsulation as something important,

 because operators don't belong to types, they should never be in their
 interface.

Well put!
Jan 07 2004
prev sibling parent reply Erik Baklund <Erik_member pathlink.com> writes:
Hi,

Using binary operators like '+', '*', etc. in mathematical expressions is
elegant and desirable. But, for some algebras the multiplicative operation is
not commutative.

Is there a way to model alternative algebras (like geometric algebra) in
D and still be able to make use of the syntactic sugar the operators provide?

In particar, it would be desirable to turn of the commutative property of the
'*' operator.

Kind regards,
Erik




In article <btfjb9$2a4k$1 digitaldaemon.com>, Walter says...
"Carlos Santander B." <carlos8294 msn.com> wrote in message
news:btfh6d$26rc$1 digitaldaemon.com...
 "Walter" <walter digitalmars.com> wrote in message
 news:btf3no$1hk9$2 digitaldaemon.com...
 |
 |
 | Of course. But how member operator lookup works is:
 |
 |     a + b
 |
 | If a is an instance of Foo, then Foo.opAdd(b) is looked for. Foo can be

 | another module. No special ADL rule is required.
 |
 |

 What if all operators can be reversed? That way, if there's no
 Foo.opAdd(typeof(b)), the compiler will look for typeof(b).opAdd_r(Foo).

Since add is commutative, the compiler will first look for typeof(a).opAdd, if that isn't found, it will look for typeof(b).opAdd.
 Also, those who want to possibly extend a stream class, would do
 MyType.opAdd_r(Stream). What about that?

I philosophically object to using operator overloading for stream I/O <g>.

Jan 07 2004
parent Antti =?iso-8859-1?Q?Syk=E4ri?= <jsykari gamma.hut.fi> writes:
And as well, the additive operation need not be commutative if you
consider '+' to denote the binary operator of a monoid (for which the
set of Java Strings with '+' as binary operator would be an example;
http://mathworld.wolfram.com/Monoid.html)

The commutativity of + and * is in the eye of the beholder.

(By the way, while * in linear algebra is not commutative it is
associative -- (A * B ) * C == A * (B * C) -- so maybe it would be best
denoted by the behavior of the concatenation operator '~'! -- Just
kidding.)

-Antti

In article <bthcg2$21eo$1 digitaldaemon.com>, Erik Baklund wrote:
 
 Hi,
 
 Using binary operators like '+', '*', etc. in mathematical expressions is
 elegant and desirable. But, for some algebras the multiplicative operation is
 not commutative.
 
 Is there a way to model alternative algebras (like geometric algebra) in
 D and still be able to make use of the syntactic sugar the operators provide?
 
 In particar, it would be desirable to turn of the commutative property of the
 '*' operator.
 
 Kind regards,
 Erik
 
 
 
 
 In article <btfjb9$2a4k$1 digitaldaemon.com>, Walter says...
"Carlos Santander B." <carlos8294 msn.com> wrote in message
news:btfh6d$26rc$1 digitaldaemon.com...
 "Walter" <walter digitalmars.com> wrote in message
 news:btf3no$1hk9$2 digitaldaemon.com...
 |
 |
 | Of course. But how member operator lookup works is:
 |
 |     a + b
 |
 | If a is an instance of Foo, then Foo.opAdd(b) is looked for. Foo can be

 | another module. No special ADL rule is required.
 |
 |

 What if all operators can be reversed? That way, if there's no
 Foo.opAdd(typeof(b)), the compiler will look for typeof(b).opAdd_r(Foo).

Since add is commutative, the compiler will first look for typeof(a).opAdd, if that isn't found, it will look for typeof(b).opAdd.
 Also, those who want to possibly extend a stream class, would do
 MyType.opAdd_r(Stream). What about that?

I philosophically object to using operator overloading for stream I/O <g>.


Jan 07 2004
prev sibling parent davepermen <davepermen_member pathlink.com> writes:
this is great. for OWN TYPES.

i'm still not able to add streaming capabilities to ANY type. types of
libraries, types of a coworker, etc. you can't add streaming capability to some
type without eighter change the stream class (just stupid), or the type class
itself (just as stupid if its not your type).

streaming capability (as any operators actually) physically, and
phylosophically, don't belong to a type. they are an algorithm, working on 2
types, wich they don't belong to. they just use them. that is basic oo, that is
basic encapsulation. you can read a lot about it on cuj.com if you want.

don't make it a memberfunction if you don't need to. that is first rule of real
oo and encapsulation.

In article <btfh6d$26rc$1 digitaldaemon.com>, Carlos Santander B. says...
"Walter" <walter digitalmars.com> wrote in message
news:btf3no$1hk9$2 digitaldaemon.com...
|
|
| Of course. But how member operator lookup works is:
|
|     a + b
|
| If a is an instance of Foo, then Foo.opAdd(b) is looked for. Foo can be in
| another module. No special ADL rule is required.
|
|

What if all operators can be reversed? That way, if there's no
Foo.opAdd(typeof(b)), the compiler will look for typeof(b).opAdd_r(Foo).
Also, those who want to possibly extend a stream class, would do
MyType.opAdd_r(Stream). What about that?

-----------------------
Carlos Santander Bernal

Jan 07 2004
prev sibling parent reply Hauke Duden <H.NS.Duden gmx.net> writes:
Walter wrote:
The problem is if opAdd() is defined in another module that is not the
current module. It will not be found with the normal scoped lookup. With
ordinary functions, one just does:

    foo.opAdd(a,b)

if the function is in module foo. That doesn't work out so good for


operator
overloads. What ADL does is look at the types of the arguments to the
function, and uses those types to add more scopes to look for the opAdd


in.
Would that even be sufficient? Doesn't one expect to be able to write an
opAdd that works on objects whose classes are defined in other modules?

Of course. But how member operator lookup works is: a + b If a is an instance of Foo, then Foo.opAdd(b) is looked for. Foo can be in another module. No special ADL rule is required.

Yes, I understand that. But I still don't see how this issue is different from having "normal" overloaded global functions. If I have two modules, foo and bar, each with a different version of function f, and I call f in another module, isn't that the exact same problem, as if f was an opAdd overload and the call an expression of the kind (a+b)? I really don't see how global operators are different from global functions! Hauke
Jan 07 2004
parent reply Ilya Minkov <minkov cs.tum.edu> writes:
Hauke Duden wrote:
 Yes, I understand that. But I still don't see how this issue is 
 different from having "normal" overloaded global functions.

Read the manual. Overloads are only possible within one scope. That means, when defined on the module level, overloads only work correctly within that module. If defined within a class, only within a class. And so on.
 If I have two modules, foo and bar, each with a different version of 
 function f, and I call f in another module, isn't that the exact same 
 problem, as if f was an opAdd overload and the call an expression of the 
 kind (a+b)?

The problem is, it doesn't work.
 I really don't see how global operators are different from global 
 functions!

They are not. That's the reason cross-module overloads don't work. They don't work for normal functions. And for operators, it is simply bloody forbidden to use module scope, because it would not make the problem better, just worse! -eye
Jan 07 2004
next sibling parent reply Hauke Duden <H.NS.Duden gmx.net> writes:
Ilya Minkov wrote:
 Hauke Duden wrote:
 
 Yes, I understand that. But I still don't see how this issue is 
 different from having "normal" overloaded global functions.

Read the manual. Overloads are only possible within one scope. That means, when defined on the module level, overloads only work correctly within that module. If defined within a class, only within a class. And so on.

I wasn't aware of that - thanks for explaining. So the problem IS the same with global functions, and it is not solved there either, just circumvented. That doesn't make it any better, though :(. Hauke
Jan 07 2004
parent "Matthew" <matthew.hat stlsoft.dot.org> writes:
"Hauke Duden" <H.NS.Duden gmx.net> wrote in message
news:bti1ij$1sv$1 digitaldaemon.com...
 Ilya Minkov wrote:
 Hauke Duden wrote:

 Yes, I understand that. But I still don't see how this issue is
 different from having "normal" overloaded global functions.

Read the manual. Overloads are only possible within one scope. That means, when defined on the module level, overloads only work correctly within that module. If defined within a class, only within a class. And so on.

I wasn't aware of that - thanks for explaining. So the problem IS the same with global functions, and it is not solved there either, just circumvented. That doesn't make it any better, though :(.

Agreed.
Jan 07 2004
prev sibling next sibling parent reply "Ben Hinkle" <bhinkle4 juno.com> writes:
"Ilya Minkov" <minkov cs.tum.edu> wrote in message
news:bthrlf$2p6n$2 digitaldaemon.com...
 Hauke Duden wrote:
 Yes, I understand that. But I still don't see how this issue is
 different from having "normal" overloaded global functions.

Read the manual. Overloads are only possible within one scope. That means, when defined on the module level, overloads only work correctly within that module. If defined within a class, only within a class. And so on.
 If I have two modules, foo and bar, each with a different version of
 function f, and I call f in another module, isn't that the exact same
 problem, as if f was an opAdd overload and the call an expression of the
 kind (a+b)?

The problem is, it doesn't work.

Even if you import foo and bar?? I thought that was the whole point of import - to bring symbols into the current scope. Is anyone really suggesting that this mechanism should work *without* requiring import statements?
 I really don't see how global operators are different from global
 functions!

They are not. That's the reason cross-module overloads don't work. They don't work for normal functions. And for operators, it is simply bloody forbidden to use module scope, because it would not make the problem better, just worse!

Again, that is what "import" is for.
Jan 08 2004
parent reply Ilya Minkov <minkov cs.tum.edu> writes:
Ben Hinkle wrote:
 Even if you import foo and bar?? I thought that was the whole point of
 import - to bring symbols into the current scope.
 Is anyone really suggesting that this mechanism should work *without*
 requiring import statements?

Import doesn't bring a module into your scope. It only adds it to module search path, used when searching for function calls. All of the overloads of one function must be completed within 1 module. We had been already arguing with Walter about that. And that this approach is, well, at least somewhat "surprising". If you make overloads of one function in one module, and some in another, and import both in yours, only one set of overloads should work. The other should be acessible through the module name point notation only.
They are not. That's the reason cross-module overloads don't work. They
don't work for normal functions. And for operators, it is simply bloody
forbidden to use module scope, because it would not make the problem
better, just worse!


 Again, that is what "import" is for.

Again, you don't seem to listen, nor to read! -eye.
Jan 09 2004
next sibling parent "Ben Hinkle" <bhinkle4 juno.com> writes:
"Ilya Minkov" <minkov cs.tum.edu> wrote in message
news:btmmt0$12nl$1 digitaldaemon.com...
 Ben Hinkle wrote:
 Even if you import foo and bar?? I thought that was the whole point of
 import - to bring symbols into the current scope.
 Is anyone really suggesting that this mechanism should work *without*
 requiring import statements?

Import doesn't bring a module into your scope. It only adds it to module search path, used when searching for function calls. All of the overloads of one function must be completed within 1 module. We had been already arguing with Walter about that. And that this approach is, well, at least somewhat "surprising". If you make overloads of one function in one module, and some in another, and import both in yours, only one set of overloads should work. The other should be acessible through the module name point notation only.

Importing a modules adds all the toplevel declarations to the current scope *but* what I had not considered was that functions are not exempt from the rule about ambiguous symbols (which if this is what you mean then yes, I think that is a problem with the language since that is the whole point of function overloading). Using aliases after importing does work with the current language spec, though I was assuming it wasn't needed for functions. For example: file1.d: import file2; alias file2.func func; import file3; alias file3.func func; int main(char[][] args) { func(1); func("hello"); return 0; } file2.d void func(char[] x) { printf("char[] func\n"); } file3.d: void func(int x) { printf("int func\n"); } ugh-ly.
They are not. That's the reason cross-module overloads don't work. They
don't work for normal functions. And for operators, it is simply bloody
forbidden to use module scope, because it would not make the problem
better, just worse!


 Again, that is what "import" is for.

Again, you don't seem to listen, nor to read!

ouch!
 -eye.

Jan 09 2004
prev sibling next sibling parent Lewis <dethbomb hotmail.com> writes:
Ilya Minkov wrote:
 Ben Hinkle wrote:

 search path, used when searching for function calls. All of the 
 overloads of one function must be completed within 1 module.
 

thats good to know, thanks
Jan 09 2004
prev sibling parent "Sean L. Palmer" <palmer.sean verizon.net> writes:
What, do you have to use ..?

with (myalgebra.opAdd)
{
    myalgebra.vecter a,b,c;
    a = b + c;
}

that's the thing with C++, is that all operators are visible all the time,
once they're imported, and the only restriction is that the types have to
match one of the overloads or be convertible.  You could go and choose one
specifically, but you could obtain the same effect by casting the inputs.
Most of the time you want all visible scopes searched for operators, not
only an "outward" search that stops at the first match.

I find this restrictive on enums as well.  I should have to specify a
particular enum type name only if the enum type actually has a name, or only
if there is some conflict and I have to choose.

Please get the visibility rules right.

Sean

"Ilya Minkov" <minkov cs.tum.edu> wrote in message
news:btmmt0$12nl$1 digitaldaemon.com...
 Ben Hinkle wrote:
 Even if you import foo and bar?? I thought that was the whole point of
 import - to bring symbols into the current scope.
 Is anyone really suggesting that this mechanism should work *without*
 requiring import statements?

Import doesn't bring a module into your scope. It only adds it to module search path, used when searching for function calls. All of the overloads of one function must be completed within 1 module. We had been already arguing with Walter about that. And that this approach is, well, at least somewhat "surprising". If you make overloads of one function in one module, and some in another, and import both in yours, only one set of overloads should work. The other should be acessible through the module name point notation only.
They are not. That's the reason cross-module overloads don't work. They
don't work for normal functions. And for operators, it is simply bloody
forbidden to use module scope, because it would not make the problem
better, just worse!


 Again, that is what "import" is for.

Again, you don't seem to listen, nor to read! -eye.

Jan 11 2004
prev sibling parent "Walter" <newshound digitalmars.com> writes:
"Ilya Minkov" <minkov cs.tum.edu> wrote in message
news:bthrlf$2p6n$2 digitaldaemon.com...
 Hauke Duden wrote:
 Read the manual. Overloads are only possible within one scope. That
 means, when defined on the module level, overloads only work correctly
 within that module. If defined within a class, only within a class. And
 so on.

 If I have two modules, foo and bar, each with a different version of
 function f, and I call f in another module, isn't that the exact same
 problem, as if f was an opAdd overload and the call an expression of the
 kind (a+b)?

The problem is, it doesn't work.

You can make it work by using an alias to overload a function in one module's scope with a function in another module's scope: import bar; void foo(int); alias bar.foo(uint) foo; Of course, this has to be done deliberately rather than implicitly. There isn't a global scope in D, which I think is a good thing when managing very large programs.
May 13 2004
prev sibling parent reply davepermen <davepermen_member pathlink.com> writes:
if we disallow member-operators (wich i don't see useful anyways), then each
operator has an unique name, and can only exist one time. i think you should
simply drop any modularisation for operators. they are syntactic glue, nothing
more.

else we just use a using statement to import the operators of some module.
shouldn't be that hard..

and else, just ADL around.. if needed. the feature is essencial for oo design,
as well as for generic design.

nobody wants to write

output(output(output(output(buffer,"Hello, my name is "),name),", and i come
from "),country);

i see a one-one mapping from function to operator. i see why you don't. but i
think the same rules can simply be applied..

and if you need the operators of some math library, wich you can only access by
math.someFunction, how about this?

with(math) {
a += b;
}

(as a proposed using statement..)

and else..

a math.+= b; doesn't look _THAT_ bad :D

so you would not require the ADL thingy at all.. or too ugly?

In article <bte4a7$1je$1 digitaldaemon.com>, Walter says...
"davepermen" <davepermen_member pathlink.com> wrote in message
news:bte3p3$pp$1 digitaldaemon.com...
 i don't see how the operator overloading can result in more work to "look

 if the parser finds some code snipped like this:

 a + b

 then it simply translates it to

 opAdd(a,b)

 directly. and then its just a normal function call.. and done. the normal
 operator rules for encoding/parsing code-lines with operators still apply,

 the endresult is pleasing, and with no issues in parsing at all..

The problem is if opAdd() is defined in another module that is not the current module. It will not be found with the normal scoped lookup. With ordinary functions, one just does: foo.opAdd(a,b) if the function is in module foo. That doesn't work out so good for operator overloads. What ADL does is look at the types of the arguments to the function, and uses those types to add more scopes to look for the opAdd in.

Jan 06 2004
parent reply "Matthew" <matthew.hat stlsoft.dot.org> writes:
 if we disallow member-operators (wich i don't see useful anyways), then

 operator has an unique name, and can only exist one time. i think you

 simply drop any modularisation for operators. they are syntactic glue,

 more.

<snip> How so? If you define a String class, and I define a Date class, are you saying that D should not support the ability for us to integrate them into, say, a generic serialisation library? Or to concatenate your string and my string? Or to work with your Transformation function and my Matrix? Is D to be extensible by users, like C and C++, or only by DM, like Java?
Jan 06 2004
next sibling parent reply Ilya Minkov <minkov cs.tum.edu> writes:
Matthew wrote:
 How so? If you define a String class, and I define a Date class, are you
 saying that D should not support the ability for us to integrate them into,
 say, a generic serialisation library? Or to concatenate your string and my
 string? Or to work with your Transformation function and my Matrix?

I think a workaround with RTTI (safe cast) and interfaces can be found for the rare cases where it matters. For example for strings and for the IO streams library. This would also mean that we could have a great advantage, if structs would be able to implement interfaces and could be casted to classes (of the same name) using automatic boxing. You would be advised to look at the library design of such a smart, pure object-oriented, multiple-inheritance, interface-based, add more buzwords here, language as Sather. ;) Bottom line: ADS is not necessary. Non-member operators are not necessary. Some thinking is. -eye
Jan 06 2004
parent davepermen <davepermen_member pathlink.com> writes:
Bottom line: ADS is not necessary. Non-member operators are not 
necessary. Some thinking is.

show me how.
Jan 06 2004
prev sibling parent davepermen <davepermen_member pathlink.com> writes:
unique per type-pair

thats what i ment.

In article <bteaec$akr$3 digitaldaemon.com>, Matthew says...
 if we disallow member-operators (wich i don't see useful anyways), then

 operator has an unique name, and can only exist one time. i think you

 simply drop any modularisation for operators. they are syntactic glue,

 more.

<snip> How so? If you define a String class, and I define a Date class, are you saying that D should not support the ability for us to integrate them into, say, a generic serialisation library? Or to concatenate your string and my string? Or to work with your Transformation function and my Matrix? Is D to be extensible by users, like C and C++, or only by DM, like Java?

Jan 06 2004
prev sibling next sibling parent "Matthew" <matthew.hat stlsoft.dot.org> writes:
Hear, hear!

"davepermen" <davepermen_member pathlink.com> wrote in message
news:btbq8o$2g9e$1 digitaldaemon.com...
 Walter

 this is essencial. not only for streaming libraries, but simply in

 Tc = Ta + Tb doesn't have ANY preference for any of those 3 types. it

 Tc opAdd(Ta a,Tb b) {}

 and not

 Tc Ta.opAdd(Tb b) {}

 or

 Tc Tb.opAddR(Ta a) {}

 or even worse, both!

 just get rid of it. binary operators don't belong to a member. they never

 and if you release that essencial missconcept, and replace it by the only

 one, we gain immediate access to a very fast implementable typesave, and
 extendable streaming library..

 Tc operator add(Ta a,Tb b) { }

 would be my prefered way.

Jan 05 2004
prev sibling parent Mark T <Mark_member pathlink.com> writes:
It seems that operator overloads should belong at the module level since they
are really just syntactic sugar for functions.
The module would define and "export" the types then the module would define and
"export" the allowed operators on those types. Since D has strong typedefs this
shouldn't be too much of a problem.
This should also allow operator overload mixtures of standard primitive types
with module defined types. If you need to add more types and operators then the
module needs changing, since you have changed the specification of that module.
Of course, the module should be cohesive but that is up to the developer.

See Ada for a language that has been doing this for a long time:
http://www.grammatech.com/rm95html-1.0/rm9x-06-06.html
http://www.adaic.com/docs/95style/html/sec_5/5-7-4.html



In article <btbq8o$2g9e$1 digitaldaemon.com>, davepermen says...
Walter

this is essencial. not only for streaming libraries, but simply in general.. an

Tc = Ta + Tb doesn't have ANY preference for any of those 3 types. it should be

Tc opAdd(Ta a,Tb b) {}

and not

Tc Ta.opAdd(Tb b) {}

or

Tc Tb.opAddR(Ta a) {}

or even worse, both!

just get rid of it. binary operators don't belong to a member. they never did.

and if you release that essencial missconcept, and replace it by the only real
one, we gain immediate access to a very fast implementable typesave, and
extendable streaming library..

Tc operator add(Ta a,Tb b) { }

would be my prefered way.

Jan 07 2004