digitalmars.D - Proposal: Implicit type conversion for structs
- Jaak (58/58) Oct 25 2006 Hello
- Bill Baxter (29/122) Oct 25 2006 Maybe you can try to explain this once more in a format of
Hello I propose the following extension to structs: One can define methods for struct T of the type: T opImplicitConvert(T2 t) which allows the implicit conversion of variables of type T2 to type T. (the name itself is not important, others possibilities: opImpConvFrom, opICast_r, etc.) A conversion does no make sense if the types T2 and T are the same, as that can cause an infinite chain of conversions. i.e. T opImplicitConvert(T t) is illegal We now get opAssign semantics without breaking or changing the way assignments currently work. Example code: struct Vector { float[] elems; ... Vector opImplicitConvert(float[] a) { Vector v; v.elems = a.dup; return v; } } ... Vector u, v; // Current system u = v; // Currently OK u = [1f 2f 3f]; // ERROR // Proposed system u = v; // Still OK v = [1f 2f 3f]; // OK, as Vector.opImplicitConvert(float[]) exists Some possible enhancements: 1. A smarter compiler may be able to figure out conversions chains if a direct conversion is not possible. I.e. MyVector a; YourVector b; HisVector c; // uses Vector.opImpConv(YourVector.opImpConv(HisVector)) if // Vector.opImpConv(HisVector) does not exist a = c; 2. If property syntax can be used to add methods to structs, one can do the following: Vector opImplicitConvert(Vector, OtherVector w) { Vector t; t.elems = w.elems; return t; } // Then, variables of type OtherVector can be implicitly // converted to type Vector. This is especially useful if // the source implementing the struct Vector is not available. OtherVector w; v = w; // expands to v = v.opImplicitConvert(w); Any comments? - Jaak
Oct 25 2006
Maybe you can try to explain this once more in a format of 1. Here's a problem currently faced by developers 2. here's a suggestion for how to fix it 3. heres how my suggestion resolves that problem 4. here's why I think this is the best/only way to resolve the problem. Reading your proposal, it's not clear what problem you're trying to solve to begin with, so it's hard to get excited about the solution. It's also difficult for others to offer you alternative solutions to the problem, not knowing what the problem is to begin with. That said, the proposal sounds reminiscent of C++'s overloadable (implicit) conversion operators, which have caused me more trouble than benefit. E.g. operator float() const { return aFloat; } Item 5 from Scott Meyer's Effective C++: "Be wary of user-defined conversion functions ...you usually don't want to provide type conversion functions of *any* ilk. The fundamental problem is that such functions often end up being called when you neither want nor expect them to be. The result can be incorrect and unintuitive program behavior that is maddeningly difficult to diagnose. ... In general, the more experience C++ programmers have, the more likely they are to eschew type conversion operators." In C++ you can now label these conversion functions as 'explicit', meaning the above would require and explicit cast (i.e. (float)anObject) to invoke it, but at that point you might as well avoid the magic syntax and just provide a toFloat() method that will be both more obvious to readers of the code and easier to find for users of your API trying to find that functionality. --bb Jaak wrote:Hello I propose the following extension to structs: One can define methods for struct T of the type: T opImplicitConvert(T2 t) which allows the implicit conversion of variables of type T2 to type T. (the name itself is not important, others possibilities: opImpConvFrom, opICast_r, etc.) A conversion does no make sense if the types T2 and T are the same, as that can cause an infinite chain of conversions. i.e. T opImplicitConvert(T t) is illegal We now get opAssign semantics without breaking or changing the way assignments currently work. Example code: struct Vector { float[] elems; ... Vector opImplicitConvert(float[] a) { Vector v; v.elems = a.dup; return v; } } ... Vector u, v; // Current system u = v; // Currently OK u = [1f 2f 3f]; // ERROR // Proposed system u = v; // Still OK v = [1f 2f 3f]; // OK, as Vector.opImplicitConvert(float[]) exists Some possible enhancements: 1. A smarter compiler may be able to figure out conversions chains if a direct conversion is not possible. I.e. MyVector a; YourVector b; HisVector c; // uses Vector.opImpConv(YourVector.opImpConv(HisVector)) if // Vector.opImpConv(HisVector) does not exist a = c; 2. If property syntax can be used to add methods to structs, one can do the following: Vector opImplicitConvert(Vector, OtherVector w) { Vector t; t.elems = w.elems; return t; } // Then, variables of type OtherVector can be implicitly // converted to type Vector. This is especially useful if // the source implementing the struct Vector is not available. OtherVector w; v = w; // expands to v = v.opImplicitConvert(w); Any comments? - Jaak
Oct 25 2006
Bill Baxter wrote:Maybe you can try to explain this once more in a format of 1. Here's a problem currently faced by developersThe main idea is achieving transparent interoperability of types. When two types are compatible (defined by the existence of suitable conversion functions) one can use them as if they are the same type and all the magic happens in the background. For example: VectorTypeA a; VectorTypeB b1, b2; a = b1 + b2; And as a side effect one gets effects similar to having an overloadable opAssign operators without changing assignments that currently compile. i.e. Vector v = [1f, 2f, 3f]2. here's a suggestion for how to fix it 3. heres how my suggestion resolves that problemIts, basically a way of specifying implicit conversions between types. Structs (or arrays using property notation) define a function which converts from one type to another: TO opIConvert(FROM what){...} If a suitable function exist, it gets called in cases when type TO is expected and type FROM is given. (more details in parent post)4. here's why I think this is the best/only way to resolve the problem.Well... I was hoping someone in here can point me to a better solution if one exists.Reading your proposal, it's not clear what problem you're trying to solve to begin with, so it's hard to get excited about the solution. It's also difficult for others to offer you alternative solutions to the problem, not knowing what the problem is to begin with.Aah sorry, I hope this post clarifies things a bit.That said, the proposal sounds reminiscent of C++'s overloadable (implicit) conversion operators, which have caused me more trouble than benefit. E.g. operator float() const { return aFloat; }It my seems like my proposal is a reversed version of this. I.e. the conversion operators are defined in the target type instead of the source type (because overloading based on return type is not allowed).Item 5 from Scott Meyer's Effective C++: "Be wary of user-defined conversion functions ....you usually don't want to provide type conversion functions of *any* ilk. The fundamental problem is that such functions often end up being called when you neither want nor expect them to be. The result can be incorrect and unintuitive program behavior that is maddeningly difficult to diagnose. ... In general, the more experience C++ programmers have, the more likely they are to eschew type conversion operators."I have no experience of this, but i can see how automatic invisible function calls all over the place can complicate debugging. But.... it would still be nice to have the option.In C++ you can now label these conversion functions as 'explicit', meaning the above would require and explicit cast (i.e. (float)anObject) to invoke it, but at that point you might as well avoid the magic syntax and just provide a toFloat() method that will be both more obvious to readers of the code and easier to find for users of your API trying to find that functionality.Any explicit declaration of intent would sort of defeat the purpose of the proposal. The aim of which is mainly to make using different types together less verbose (and get opAssign functionality). Anyway, its not a dealbreaker, just on my wishlist.--bb
Oct 25 2006
On Thu, 26 Oct 2006 06:15:11 +0300, Jaak <jpdt telkomsa.net> wrote:Bill Baxter wrote:[snip]That said, the proposal sounds reminiscent of C++'s overloadable =an =(implicit) conversion operators, which have caused me more trouble th=Hmm, I have to say that the both sides have good points. Implicit type = casting can cause trouble. But in the other hand, it can be also very = useful in some situations, i.e. when the types are indeed compatible. In C++ I have had problems with signed/unsigned type casting. I can't ju= st = now recall what they were, but they were annoying, and the compiler shou= ld = have got them right. I think implicit type conversion can be made a lot safer (than it's in = C++ and in any other language I know of). It would require type concersi= on = rules. That is, more than simply telling the compiler that here are all = = the conversion operators, use them as you wish. For example, in C++: class C; class A { A &operator =3D(int); A &operator =3D(const C &); }; class B { operator int() const; operator C() const; }; void func() { A a; B b; a =3D b; } Should 'b' to converted to 'int' or 'C'? There is no way of knowing that= . However, if one could define that 'C' is prefered over 'int', there woul= d = be no problems. For example, the order in which the operator are defined would tell the = = preference order (or you could use some kind of numeric value, etc): class B { operator C() const; //1. operator int() const; //2. }; There could be some other rules as well. I mean, human can always tell = that what conversion should be used, if possible. Why not tell that to = compiler also? Is it possible to create such rules in compact way that would apply = (almost) all the cases? Is the preference ordering enough?benefit. E.g. operator float() const { return aFloat; }
Oct 26 2006
One could possibly require that the shortest conversion chain is always used, and it is an error if a conversion is required and more than one conversion chains of the same length exists. In your example below A and B are implicitly compatible by two possible chains of conversions: B => int => C B => C => A This could be flagged as ambigious. Making the compatibility explicit would remove the error. In the below example: Add to class A a conversion from B: A &operator =(const B &); Add to class B a conversion to B: operator A() const; Its a tradeoff: no need for preference ordering, at the cost of having to define extra conversion functions. The shortest conversion chain is then simply: B => A Kristian wrote:On Thu, 26 Oct 2006 06:15:11 +0300, Jaak <jpdt telkomsa.net> wrote:I've been bitten by that in D as well: if (array.length-5>0) // is always true as length property is unsigned But this is luckily not a mistake one keeps on making.Bill Baxter wrote:[snip]Hmm, I have to say that the both sides have good points. Implicit type casting can cause trouble. But in the other hand, it can be also very useful in some situations, i.e. when the types are indeed compatible. In C++ I have had problems with signed/unsigned type casting. I can't just now recall what they were, but they were annoying, and the compiler should have got them right.That said, the proposal sounds reminiscent of C++'s overloadable (implicit) conversion operators, which have caused me more trouble than benefit. E.g. operator float() const { return aFloat; }I think implicit type conversion can be made a lot safer (than it's in C++ and in any other language I know of). It would require type concersion rules. That is, more than simply telling the compiler that here are all the conversion operators, use them as you wish. For example, in C++: class C; class A { A &operator =(int); A &operator =(const C &); }; class B { operator int() const; operator C() const; }; void func() { A a; B b; a = b; } Should 'b' to converted to 'int' or 'C'? There is no way of knowing that.. However, if one could define that 'C' is prefered over 'int', there would be no problems. For example, the order in which the operator are defined would tell the preference order (or you could use some kind of numeric value, etc): class B { operator C() const; //1. operator int() const; //2. }; There could be some other rules as well. I mean, human can always tell that what conversion should be used, if possible. Why not tell that to compiler also? Is it possible to create such rules in compact way that would apply (almost) all the cases? Is the preference ordering enough?
Oct 27 2006